@peaceroad/markdown-it-figure-with-p-caption 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +596 -579
  2. package/index.js +290 -167
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,579 +1,596 @@
1
- # p7d-markdown-it-figure-with-p-caption
2
-
3
- This markdown-it plugin converts paragraphs representing captions before or after image/table/code/video/audio/iframe into `figcaption` element, and wraps them in `figure` element. Caption parsing (labels, filenames, spacing rules) is delegated to [`p7d-markdown-it-p-captions`](https://www.npmjs.com/package/p7d-markdown-it-p-captions), so this plugin focuses on detecting the surrounding structure. Optionally, you have the option to wrap it in a `figure` element, even if there is no caption paragraph.
4
-
5
- For images, even if they don't have a caption paragraph, they can be treated as captions if they have a caption string in the image's `alt`/`title` text (there is also an option to promote them to captions even if they don't have that string).
6
-
7
- Optionally, you can auto-number image and table caption paragraphs starting from the beginning of the document if they only have label names.
8
-
9
- **Note.** If you want to adjust the image `width`/`height`, please also use [`@peaceroad/markdown-it-renderer-image`](https://www.npmjs.com/package/@peaceroad/markdown-it-renderer-image). Also, if you want to use the `samp` element when displaying terminal output, please also use [`@peaceroad/markdown-it-renderer-fence`](https://www.npmjs.com/package/@peaceroad/markdown-it-renderer-fence). This document shows output using the latter option.
10
-
11
- ## Behavior
12
-
13
- ### Image
14
-
15
- - Pure image paragraphs (`![...](...)`) become `<figure class="f-img">` blocks as soon as a caption paragraph (previous or next) or an auto-detected caption exists.
16
- - Auto detection runs per image paragraph when `autoCaptionDetection` is `true` (default). The priority is:
17
- 1. Caption paragraphs immediately before or after the image (standard syntax).
18
- 2. Image `alt` text that already matches p7d-markdown-it-p-captions label formats (`Figure. `, `Figure 1. `, `図 `,`図1 `, etc.).
19
- 3. Image `title` attribute that matches the same labels.
20
- 4. Optional fallbacks (`autoAltCaption`, `autoTitleCaption`) that inject the label when the alt/title lacks one.
21
- - `autoAltCaption`: `false` (default), `true`, or a string label. `true` inspects the first sentence of the caption text and picks `Figure` / `図` based on detected language; a string uses that label verbatim.
22
- - `autoTitleCaption`: same behavior but sourced from the image `title`. It stays off by default so other plugins can keep using the `title` attribute for metadata.
23
- - Set `autoCaptionDetection: false` to disable the auto-caption workflow entirely.
24
- - Multi-image paragraphs are still wrapped as one figure when `multipleImages: true` (default). Layout-specific classes help with styling:
25
- - `f-img-horizontal` when images sit on the same line (space-delimited).
26
- - `f-img-vertical` when separated only by soft breaks.
27
- - `f-img-multiple` for mixed layouts.
28
- - Automatic detection inspects only the first image in the paragraph. If it yields a caption, the entire figure reuses that caption while later images keep their own `alt`/`title`.
29
- - Paragraphs that contain only images also convert when they appear inside loose lists (leave blank lines between items), blockquotes, or description lists.
30
-
31
- ### Table
32
-
33
- - Markdown tables (including those produced by `markdown-it-multimd-table` or similar) convert into `<figure class="f-table">` blocks.
34
- - Caption paragraphs immediately before/after the table become `<figcaption>` element ahead of the `<table>`.
35
-
36
- ### Code block
37
-
38
- - Captions labeled `Code. `, `Terminal. `, etc. wrap the fence in `<figure class="f-pre-code">` / `<figure class="f-pre-samp">`.
39
- - If `roleDocExample: true`, these figures add `role="doc-example"`.
40
-
41
- ### Blockquote
42
-
43
- - Captioned blockquotes (e.g., “Source. A paragraph. Ewritten immediately before or after `> ...`) become `<figure class="f-blockquote">` while keeping the original blockquote intact.
44
-
45
- ### Video & Audio
46
-
47
- - Inline HTML `<video>` and `<audio>` tags are detected as media figures (`<figure class="f-video">` and `<figure class="f-audio">`).
48
- - A caption paragraph labeled `Video. ` / `Audio. ` (or any registered label) is promoted to `<figcaption>` before/after the media so controls remain unobstructed.
49
-
50
- ### Embedded content by iframe
51
-
52
- - Inline HTML `<iframe>` elements become `<figure class="f-video">` when they point to known video hosts (YouTube `youtube.com` / `youtube-nocookie.com`, Vimeo `player.vimeo.com`).
53
- - Blockquote-based social embeds (Twitter/X `twitter-tweet`, Mastodon `mastodon-embed`, Bluesky `bluesky-embed`, Instagram `instagram-media`, Tumblr `text-post-media`) are treated like iframe-type embeds when their `class` matches those providers. By default they become `<figure class="f-img">` so the caption label behaves like an image label (Labels can also use quote labels). You can override that figure class with `figureClassThatWrapsIframeTypeBlockquote` or the global `allIframeTypeFigureClassName`.
54
- - `p7d-markdown-it-p-captions` ships with a `Slide.` label. When you use it (for example with Speaker Deck or SlideShare iframes), the `<figure>` wrapper automatically switches to `f-slide` (or whatever you set via `figureClassThatWrapsSlides`) so slides can get their own layout. If `allIframeTypeFigureClassName` is also configured, that class takes precedence even for slides, so you get a uniform embed wrapper without touching the slide option.
55
- - All other iframes fall back to `<figure class="f-iframe">` unless you override the class via `allIframeTypeFigureClassName`.
56
-
57
- ### label span class name
58
-
59
- - The label inside the figcaption (the `span` element used for the label) is generated by `p7d-markdown-it-p-captions`, not by this plugin. By default the class name is formed by combining `classPrefix` with the mark name, producing names such as `f-img-label`, `f-video-label`, `f-blockquote-label`, and `f-slide-label`.
60
- - With `markdown-it-attrs`, any attribute block (`{ .foo #bar }`) attached to the caption paragraph is moved to the generated `<figure>` by default (`styleProcess: true`). This keeps per-figure classes/IDs on the wrapper instead of the original paragraph; disable the option only if you explicitly want the attributes to stay on the paragraph.
61
-
62
- ## Behavior Customization
63
-
64
- ### Styles
65
-
66
- - Set `allIframeTypeFigureClassName: 'f-embed'` (recommended) to force a single CSS class across `<iframe>` and social-embed figures so they can share styles, ensuring every embed wrapper shares the same predictable class name.
67
- - `figureClassThatWrapsIframeTypeBlockquote`: override the class used when blockquote-based embeds (Twitter, Mastodon, Bluesky) are wrapped.
68
- - `figureClassThatWrapsSlides`: override the class assigned when a caption paragraph uses the `Slide.` label.
69
- - `classPrefix` (default `f`) controls the CSS namespace for every figure (`f-img`, `f-table`, etc.) so you can align with existing styles.
70
-
71
- ### Wrapping without captions
72
-
73
- - `oneImageWithoutCaption`: turn single-image paragraphs into `<figure>` elements even when no caption paragraph/auto caption is present. This is independent of automatic detection.
74
- - `videoWithoutCaption`, `audioWithoutCaption`, `iframeWithoutCaption`, `iframeTypeBlockquoteWithoutCaption`: wrap the respective media blocks without caption.
75
-
76
- ### Caption text helpers (delegated to `p7d-markdown-it-p-captions`)
77
-
78
- Every option below is forwarded verbatim to `p7d-markdown-it-p-captions`, which owns the actual figcaption rendering:
79
-
80
- - `strongFilename` / `dquoteFilename`: pull out filenames from captions using `**filename**` or `"filename"` syntax and wrap them in `<strong class="f-*-filename">`.
81
- - `jointSpaceUseHalfWidth`: replace full-width space between Japanese labels and caption body with half-width space.
82
- - `bLabel` / `strongLabel`: emphasize the label span itself.
83
- - `removeUnnumberedLabel`: drop the leading “Figure. Etext entirely when no label number is present. Use `removeUnnumberedLabelExceptMarks` to keep specific labels (e.g., `['blockquote']` keeps `Quote. `).
84
- - `removeMarkNameInCaptionClass`: replace `.f-img-label` / `.f-table-label` with the generic `.f-label`.
85
- - `wrapCaptionBody`: wrap the non-label caption text in a span element.
86
- - `hasNumClass`: add a class attribute to label span element if it has a label number.
87
- - `labelClassFollowsFigure`: mirror the resolved `<figure>` class onto the `figcaption` spans (`f-embed-label`, `f-embed-label-joint`, `f-embed-body`, etc.) when you want captions styled alongside the wrapper.
88
- - `figureToLabelClassMap`: extend `labelClassFollowsFigure` by mapping specific figure classes (e.g., `f-embed`) to custom caption label classes such as `caption-embed caption-social` for fine-grained control.
89
-
90
- ### Automatic numbering
91
-
92
- - `autoLabelNumberSets`: enable numbering per media type. Pass an array such as `['img']`, `['table']`, or `['img', 'table']`.
93
- - `autoLabelNumber`: shorthand for turning numbering on for both images and tables without passing the array yourself. Provide `autoLabelNumberSets` explicitly (e.g., `['img']`) when you need finer control—the explicit array always wins.
94
- - Counters start at `1` near the top of the document and increment sequentially per media type. Figures and tables keep independent counters even when mixed together.
95
- - The counter only advances when a real caption exists (paragraph, auto-detected alt/title, or fallback text). Figures emitted solely because of `oneImageWithoutCaption` stay unnumbered.
96
- - Manual numbers inside the caption text (e.g., `Figure 5.`) always win. The plugin updates its internal counter so the next automatic number becomes `6`. This applies to captions sourced from paragraphs, auto detection, and fallback captions.
97
-
98
- ## Basic Usage
99
-
100
- ```js
101
- import mdit from 'markdown-it'
102
- import mditFigureWithPCaption from '@peaceroad/markdown-it-figure-with-p-caption'
103
- import mditRendererFence from '@peaceroad/markdown-it-renderer-fence' // optional but keeps fences aligned with samples
104
-
105
- const md = mdit({ html: true, langPrefix: 'language-', })
106
- .use(mditFigureWithPCaption)
107
- .use(mditRendererFence)
108
-
109
- console.log(md.render('Figure. A Cat.\n\n![A cat](cat.jpg)'))
110
- // <figure class="f-img">
111
- // <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A Cat.</figcaption>
112
- // <img src="cat.jpg" alt="A cat">
113
- // </figure>
114
- ```
115
-
116
- ### Basic Recommended Options
117
-
118
- Auto label numbering for images and tables.
119
-
120
- ```js
121
- const figureOption = {
122
- // Opinionated defaults
123
- oneImageWithoutCaption: true,
124
- videoWithoutCaption: true,
125
- audioWithoutCaption: true,
126
- iframeWithoutCaption: true,
127
- iframeTypeBlockquoteWithoutCaption: true,
128
- removeUnnumberedLabelExceptMarks: ['blockquote'], // keep “Quote. Elabels even when unnumbered
129
- allIframeTypeFigureClassName: 'f-embed', // apply a uniform class to every iframe-style embed
130
- autoLabelNumber: true,
131
-
132
- // If you want to enable auto alt/title captioning fallbacks without caption label.
133
- //autoAltCaption: true,
134
- //autoTitleCaption: true,
135
- }
136
- ```
137
-
138
- If there is no label number, the label will also be deleted.
139
-
140
- ```js
141
- const figureOption = {
142
- oneImageWithoutCaption: true,
143
- videoWithoutCaption: true,
144
- audioWithoutCaption: true,
145
- iframeWithoutCaption: true,
146
- iframeTypeBlockquoteWithoutCaption: true,
147
- removeUnnumberedLabelExceptMarks: ['blockquote'],
148
- allIframeTypeFigureClassName: 'f-embed',
149
- removeUnnumberedLabel: true,
150
- }
151
- ```
152
-
153
- These options can be used as follows:
154
-
155
- ```
156
- const md = mdit({ html: true }).use(mditFigureWithPCaption, figureOption)
157
- ```
158
-
159
- ## Conversion Examples
160
-
161
- ### Default before/after caption paragraph detection
162
-
163
- ~~~
164
- [Markdown]
165
- ![A single cat](figure.jpg)
166
-
167
- [HTML]
168
- <p><img src="figure.jpg" alt="A single cat"></p>
169
-
170
- <!-- Above: If oneImageWithoutCaption is true, this img element has wrapped into figure element without caption. -->
171
-
172
-
173
- [Markdown]
174
- Figure. A Caption.
175
-
176
- ![A single cat](figure.jpg)
177
- [HTML]
178
- <figure class="f-img">
179
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A Caption.</figcaption>
180
- <img src="figure.jpg" alt="A single cat">
181
- </figure>
182
-
183
-
184
- [Markdown]
185
- ![A single cat](figure.jpg)
186
-
187
- Figure. A Caption.
188
- [HTML]
189
- <figure class="f-img">
190
- <img src="figure.jpg" alt="A single cat">
191
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A Caption.</figcaption>
192
- </figure>
193
-
194
-
195
- [Markdown]
196
- Table. A Caption.
197
-
198
- | Tokyo | Osaka |
199
- | ----- | ----- |
200
- | Sushi | Takoyaki |
201
-
202
- [HTML]
203
- <p>A paragraph.</p>
204
- <figure class="f-table">
205
- <figcaption><span class="f-table-label">Table<span class="f-table-label-joint">.</span></span> A Caption.</figcaption>
206
- <table>
207
- <thead>
208
- <tr>
209
- <th>Tokyo</th>
210
- <th>Osaka</th>
211
- </tr>
212
- </thead>
213
- <tbody>
214
- <tr>
215
- <td>Sushi</td>
216
- <td>Takoyaki</td>
217
- </tr>
218
- </tbody>
219
- </table>
220
- </figure>
221
-
222
-
223
- [Markdown]
224
- Code. A Caption.
225
-
226
- ```js
227
- console.log('Hello World!');
228
- ```
229
-
230
- [HTML]
231
- <figure class="f-pre-code">
232
- <figcaption><span class="f-pre-code-label">Code<span class="f-pre-code-label-joint">.</span></span> A Caption.</figcaption>
233
- <pre><code class="language-js">console.log('Hello World!');
234
- </code></pre>
235
- </figure>
236
-
237
- <!-- Above: class attribute of code element is generated by markdown-it option. -->
238
-
239
-
240
- [Markdown]
241
- Source. A Caption.
242
-
243
- > A quoted paragraph.
244
-
245
- [HTML]
246
- <figure class="f-blockquote">
247
- <figcaption><span class="f-blockquote-label">Source<span class="f-blockquote-label-joint">.</span></span> A Caption.</figcaption>
248
- <blockquote>
249
- <p>A quoted paragraph.</p>
250
- </blockquote>
251
- </figure>
252
-
253
-
254
- [Markdown]
255
- Terminal. A Caption.
256
-
257
- ```samp
258
- $ pwd
259
- /home/user
260
- ```
261
-
262
- [HTML]
263
- <figure class="f-pre-samp">
264
- <figcaption><span class="f-pre-samp-label">Terminal<span class="f-pre-samp-label-joint">.</span></span> A Caption.</figcaption>
265
- <pre><samp>$ pwd
266
- /home/user
267
- </samp></pre>
268
- </figure>
269
-
270
- <!-- Above: When @peaceroad/markdown-it-renderer-fence is used, samp element are generated automatically for `samp` fences. -->
271
-
272
- [Markdown]
273
- Video. A mp4.
274
-
275
- <video controls width="400" height="300">
276
- <source src="example.mp4" type="video/mp4">
277
- </video>
278
-
279
- [HTML]
280
- <figure class="f-video">
281
- <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> A mp4.</figcaption>
282
- <video controls width="400" height="300">
283
- <source src="example.mp4" type="video/mp4">
284
- </video>
285
- </figure>
286
-
287
-
288
- [Markdown]
289
- Audio. A narration.
290
-
291
- <audio controls>
292
- <source src="example.mp3" type="audio/mpeg">
293
- </audio>
294
-
295
- [HTML]
296
- <figure class="f-audio">
297
- <figcaption><span class="f-audio-label">Audio<span class="f-audio-label-joint">.</span></span> A narration.</figcaption>
298
- <audio controls>
299
- <source src="example.mp3" type="audio/mpeg">
300
- </audio>
301
- </figure>
302
-
303
-
304
- [Markdown]
305
- Video. A YouTube video.
306
-
307
- <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
308
-
309
- [HTML]
310
- <figure class="f-video">
311
- <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> A YouTube video.</figcaption>
312
- <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
313
- </figure>
314
-
315
-
316
- [Markdown]
317
- Figure. Mastodon post.
318
-
319
- <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
320
-
321
- [HTML]
322
- <figure class="f-img">
323
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Mastodon post.</figcaption>
324
- <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
325
- </figure>
326
-
327
-
328
- [Markdown]
329
- Quote. Mastodon post.
330
-
331
- <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
332
-
333
- [HTML]
334
- <figure class="f-img">
335
- <figcaption><span class="f-blockquote-label">Quote<span class="f-blockquote-label-joint">.</span></span> X post.</figcaption>
336
- <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
337
- </figure>
338
-
339
-
340
- [Markdown]
341
- Slide. A Speaker Deck.
342
-
343
- <iframe src="https://speakerdeck.com/player/XXXXXXXXXXX" width="640" height="360" frameborder="0" allowfullscreen></iframe>
344
-
345
- [HTML]
346
- <figure class="f-slide">
347
- <figcaption><span class="f-slide-label">Slide<span class="f-slide-label-joint">.</span></span> A Speaker Deck.</figcaption>
348
- <iframe src="https://speakerdeck.com/player/XXXXXXXXXXX" width="640" height="360" frameborder="0" allowfullscreen></iframe>
349
- </figure>
350
- ~~~
351
-
352
- ### Auto alt/title detection
353
-
354
- ```
355
- [Markdown]
356
- ![Figure. A cat.](cat.jpg)
357
-
358
- [HTML]
359
- <figure class="f-img">
360
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A cat.</figcaption>
361
- <img src="cat.jpg" alt="">
362
- </figure>
363
-
364
-
365
- [Markdown]
366
- ![A white cat eats fishs.](cat.jpg "Figure. A cat.")
367
-
368
- [HTML]
369
- <figure class="f-img">
370
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A cat.</figcaption>
371
- <img src="cat.jpg" alt="A white cat eats fishs.">
372
- </figure>
373
- ```
374
-
375
- ### Multiple images
376
-
377
- ~~~
378
- [Markdown]
379
- A paragraph. multipleImages: true. horizontal images only.
380
-
381
- ![Sitting cat](cat1.jpg) ![Standing cat](cat2.jpg)
382
-
383
- Figure. Cats.
384
-
385
- A paragraph.
386
- [HTML]
387
- <p>A paragraph. multipleImages: true. horizontal images only</p>
388
- <figure class="f-img-horizontal">
389
- <img src="cat1.jpg" alt="Sitting cat"><img src="cat2.jpg" alt="Standing cat">
390
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Cats.</figcaption>
391
- </figure>
392
- <p>A paragraph.</p>
393
-
394
- [Markdown]
395
- A paragraph. multipleImages: true. vertical images only.
396
-
397
- Figure. Cats.
398
-
399
- ![Sitting cat](cat1.jpg)
400
- ![Standing cat](cat2.jpg)
401
-
402
- A paragraph.
403
- [HTML]
404
- <p>A paragraph. multipleImages: true. vertical images only.</p>
405
- <figure class="f-img-vertical">
406
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Cats.</figcaption>
407
- <img src="cat1.jpg" alt="Sitting cat">
408
- <img src="cat2.jpg" alt="Standing cat">
409
- </figure>
410
- <p>A paragraph.</p>
411
-
412
- [Markdown]
413
- A paragraph. multipleImages: true.
414
-
415
- Figure. Cats.
416
-
417
- ![Sitting cat](cat1.jpg) ![Standing cat](cat2.jpg)
418
- ![Sleeping cat](cat3.jpg)
419
-
420
- A paragraph.
421
- [HTML]
422
- <p>A paragraph. multipleImages: true.</p>
423
- <figure class="f-img-multiple">
424
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Cats.</figcaption>
425
- <img src="cat1.jpg" alt="Sitting cat"><img src="cat2.jpg" alt="Standing cat">
426
- <img src="cat3.jpg" alt="Sleeping cat">
427
- </figure>
428
- <p>A paragraph.</p>
429
- ~~~
430
-
431
- ## Option Examples
432
-
433
- ### Styles
434
-
435
- This example uses `classPrefix: 'custom'` and leaves `styleProcess: true` so a trailing `{.notice}` block moves onto the `<figure>` wrapper.
436
-
437
- ```
438
- [Markdown]
439
- Figure. Highlighted cat. {.notice}
440
-
441
- ![Highlighted cat](cat.jpg)
442
- [HTML]
443
- <figure class="custom-img notice">
444
- <figcaption><span class="custom-img-label">Figure<span class="custom-img-label-joint">.</span></span> Highlighted cat.</figcaption>
445
- <img src="cat.jpg" alt="Highlighted cat">
446
- </figure>
447
- ```
448
-
449
- ### Automatic detection fallbacks
450
-
451
- `autoCaptionDetection` combined with `autoAltCaption` / `autoTitleCaption` can still generate caption text even when the original alt/title lacks labels. The corresponding attributes are cleared after conversion so the figcaption becomes the canonical source.
452
-
453
- ```
454
- [Markdown]
455
- ![Alt fallback example](bird.jpg)
456
-
457
- [HTML]
458
- <figure class="f-img">
459
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Alt fallback example</figcaption>
460
- <img src="bird.jpg" alt="">
461
- </figure>
462
-
463
-
464
- [Markdown]
465
- ![No caption](fish.jpg "Plain title text")
466
-
467
- [HTML]
468
- <figure class="f-img">
469
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Plain title text</figcaption>
470
- <img src="fish.jpg" alt="No caption">
471
- </figure>
472
- ```
473
-
474
- ### Role helpers
475
-
476
- Set `roleDocExample: true` to add `role="doc-example"` to code/samp figures.
477
-
478
- ~~~
479
- [Markdown]
480
- ```samp
481
- $ pwd
482
- /home/user
483
- ```
484
-
485
- [HTML]
486
- <figure class="f-pre-samp" role="doc-example">
487
- ...
488
- </figure>
489
- ~~~
490
-
491
- ### Captionless conversion toggles
492
-
493
- If `oneImageWithoutCaption` is enabled, a single image paragraph will be wrapped with `<figure class="f-img">` even without a caption.
494
-
495
- ```
496
- [Markdown]
497
- ![A single cat](cat.jpg)
498
-
499
- [HTML]
500
- <figure class="f-img">
501
- <img src="cat.jpg" alt="A single cat">
502
- </figure>
503
- ```
504
-
505
- If `videoWithoutCaption` is enabled, an `iframe` pointing to a known video host (such as YouTube or video elements) will be wrapped with `<figure class="f-video">`.
506
-
507
- ```
508
- [Markdown]
509
- <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
510
-
511
- [HTML]
512
- <figure class="f-video">
513
- <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
514
- </figure>
515
-
516
-
517
- [Markdown]
518
- <video controls width="400" height="300">
519
- <source src="example.mp4" type="video/mp4">
520
- </video>
521
- [HTML]
522
- <figure class="f-video">
523
- <video controls width="400" height="300">
524
- <source src="example.mp4" type="video/mp4">
525
- </video>
526
- </figure>
527
- ```
528
-
529
- When `iframeWithoutCaption` is enabled, iframe elements will be wrapped with `<figure class="f-iframe">`. And if `iframeTypeBlockquoteWithoutCaption` is enabled, blockquote-based embeds (for example, X) will be wrapped with `<figure class="f-img">` (or another configured class).
530
-
531
- ```
532
- [Markdown]
533
- <iframe>
534
- ...
535
- </iframe>
536
-
537
- [HTML]
538
- <figure class="f-iframe">
539
- <iframe>
540
- ...
541
- </iframe>
542
- </figure>
543
- ```
544
-
545
- ### Iframe-type blockquote class override
546
-
547
- Set `figureClassThatWrapsIframeTypeBlockquote: 'f-social'` (or any class you prefer) to wrap blockquote-based embeds (for example, X, Mastodon, Bluesky) with that class.
548
-
549
- ```
550
- [Markdown]
551
- Figure. Twitter embed.
552
-
553
- <blockquote class="twitter-tweet"><p>Embed content</p></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
554
-
555
- [HTML]
556
- <figure class="f-social">
557
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Twitter embed.</figcaption>
558
- <blockquote class="twitter-tweet"><p>Embed content</p></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
559
- </figure>
560
- ```
561
-
562
- ### All iframe/embed class override
563
-
564
- Set `allIframeTypeFigureClassName: 'f-embed'` (or any class you prefer) to consolidate iframe-like embeds under one class.
565
-
566
- ```
567
- [Markdown]
568
- Video. Custom embed.
569
-
570
- <iframe width="560" height="315" src="https://example.com/embed/123" title="Custom embed" frameborder="0" allowfullscreen></iframe>
571
-
572
- [HTML]
573
- <figure class="f-embed">
574
- <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> Custom embed.</figcaption>
575
- <iframe width="560" height="315" src="https://example.com/embed/123" title="Custom embed" frameborder="0" allowfullscreen></iframe>
576
- </figure>
577
- ```
578
-
579
- Need matching caption classes too? Combine this option with `labelClassFollowsFigure` (and optionally `figureToLabelClassMap`) so the `figcaption` spans inherit the embed class you just applied (e.g., `f-embed-label`, `f-embed-label-joint`).
1
+ # p7d-markdown-it-figure-with-p-caption
2
+
3
+ This markdown-it plugin converts paragraphs representing captions before or after image/table/code/video/audio/iframe into `figcaption` element, and wraps them in `figure` element. Caption parsing (labels, filenames, spacing rules) is delegated to [`p7d-markdown-it-p-captions`](https://www.npmjs.com/package/p7d-markdown-it-p-captions), so this plugin focuses on detecting the surrounding structure. Optionally, you have the option to wrap it in a `figure` element, even if there is no caption paragraph.
4
+
5
+ For images, even if they don't have a caption paragraph, they can be treated as captions if they have a caption string in the image's `alt`/`title` text (there is also an option to promote them to captions even if they don't have that string).
6
+
7
+ Optionally, you can auto-number image and table caption paragraphs starting from the beginning of the document if they only have label names.
8
+
9
+ **Note.** If you want to adjust the image `width`/`height`, please also use [`@peaceroad/markdown-it-renderer-image`](https://www.npmjs.com/package/@peaceroad/markdown-it-renderer-image). Also, if you want to use the `samp` element when displaying terminal output, please also use [`@peaceroad/markdown-it-renderer-fence`](https://www.npmjs.com/package/@peaceroad/markdown-it-renderer-fence). This document shows output using the latter option.
10
+
11
+ ## Behavior
12
+
13
+ ### Image
14
+
15
+ - Pure image paragraphs (`![...](...)`) become `<figure class="f-img">` blocks as soon as a caption paragraph (previous or next) or an auto-detected caption exists.
16
+ - Auto detection runs per image paragraph when `autoCaptionDetection` is `true` (default). The priority is:
17
+ 1. Caption paragraphs immediately before or after the image (standard syntax).
18
+ 2. Image `alt` text that already matches p7d-markdown-it-p-captions label formats (`Figure. `, `Figure 1. `, `図 `,`図1 `, etc.).
19
+ 3. Image `title` attribute that matches the same labels.
20
+ 4. Optional fallbacks (`autoAltCaption`, `autoTitleCaption`) that inject the label when the alt/title lacks one.
21
+ - `autoAltCaption`: `false` (default), `true`, or a string label. `true` inspects the first sentence of the caption text and picks `Figure` / `図` based on detected language; a string uses that label verbatim.
22
+ - `autoTitleCaption`: same behavior but sourced from the image `title`. It stays off by default so other plugins can keep using the `title` attribute for metadata.
23
+ - Set `autoCaptionDetection: false` to disable the auto-caption workflow entirely.
24
+ - Multi-image paragraphs are still wrapped as one figure when `multipleImages: true` (default). Layout-specific classes help with styling:
25
+ - `f-img-horizontal` when images sit on the same line (space-delimited).
26
+ - `f-img-vertical` when separated only by soft breaks.
27
+ - `f-img-multiple` for mixed layouts.
28
+ - Automatic detection inspects only the first image in the paragraph. If it yields a caption, the entire figure reuses that caption while later images keep their own `alt`/`title`.
29
+ - Paragraphs that contain only images also convert when they appear inside loose lists (leave blank lines between items), blockquotes, or description lists.
30
+
31
+ ### Table
32
+
33
+ - Markdown tables (including those produced by `markdown-it-multimd-table` or similar) convert into `<figure class="f-table">` blocks.
34
+ - Caption paragraphs immediately before/after the table become `<figcaption>` element ahead of the `<table>`.
35
+
36
+ ### Code block
37
+
38
+ - Captions labeled `Code. `, `Terminal. `, etc. wrap the fence in `<figure class="f-pre-code">` / `<figure class="f-pre-samp">`.
39
+ - If `roleDocExample: true`, these figures add `role="doc-example"`.
40
+
41
+ ### Blockquote
42
+
43
+ - Captioned blockquotes (e.g., “Source. A paragraph. Ewritten immediately before or after `> ...`) become `<figure class="f-blockquote">` while keeping the original blockquote intact.
44
+
45
+ ### Video & Audio
46
+
47
+ - Inline HTML `<video>` and `<audio>` tags are detected as media figures (`<figure class="f-video">` and `<figure class="f-audio">`).
48
+ - A caption paragraph labeled `Video. ` / `Audio. ` (or any registered label) is promoted to `<figcaption>` before/after the media so controls remain unobstructed.
49
+
50
+ ### Embedded content by iframe
51
+
52
+ - Inline HTML `<iframe>` elements become `<figure class="f-video">` when they point to known video hosts (YouTube `youtube.com` / `youtube-nocookie.com`, Vimeo `player.vimeo.com`).
53
+ - Blockquote-based social embeds (Twitter/X `twitter-tweet`, Mastodon `mastodon-embed`, Bluesky `bluesky-embed`, Instagram `instagram-media`, Tumblr `text-post-media`) are treated like iframe-type embeds when their `class` matches those providers. By default they become `<figure class="f-img">` so the caption label behaves like an image label (Labels can also use quote labels). You can override that figure class with `figureClassThatWrapsIframeTypeBlockquote` or the global `allIframeTypeFigureClassName`.
54
+ - `p7d-markdown-it-p-captions` ships with a `Slide.` label. When you use it (for example with Speaker Deck or SlideShare iframes), the `<figure>` wrapper automatically switches to `f-slide` (or whatever you set via `figureClassThatWrapsSlides`) so slides can get their own layout. If `allIframeTypeFigureClassName` is also configured, that class takes precedence even for slides, so you get a uniform embed wrapper without touching the slide option.
55
+ - All other iframes fall back to `<figure class="f-iframe">` unless you override the class via `allIframeTypeFigureClassName`.
56
+
57
+ ### label span class name
58
+
59
+ - The label inside the figcaption (the `span` element used for the label) is generated by `p7d-markdown-it-p-captions`, not by this plugin. By default the class name is formed by combining `classPrefix` with the mark name, producing names such as `f-img-label`, `f-video-label`, `f-blockquote-label`, and `f-slide-label`.
60
+ - With `markdown-it-attrs`, any attribute block (`{ .foo #bar }`) attached to the caption paragraph is moved to the generated `<figure>` by default (`styleProcess: true`). This keeps per-figure classes/IDs on the wrapper instead of the original paragraph; disable the option only if you explicitly want the attributes to stay on the paragraph.
61
+
62
+ ## Behavior Customization
63
+
64
+ ### Styles
65
+
66
+ - Set `allIframeTypeFigureClassName: 'f-embed'` (recommended) to force a single CSS class across `<iframe>` and social-embed figures so they can share styles, ensuring every embed wrapper shares the same predictable class name.
67
+ - `figureClassThatWrapsIframeTypeBlockquote`: override the class used when blockquote-based embeds (Twitter, Mastodon, Bluesky) are wrapped.
68
+ - `figureClassThatWrapsSlides`: override the class assigned when a caption paragraph uses the `Slide.` label.
69
+ - `classPrefix` (default `f`) controls the CSS namespace for every figure (`f-img`, `f-table`, etc.) so you can align with existing styles.
70
+
71
+ ### Wrapping without captions
72
+
73
+ - `oneImageWithoutCaption`: turn single-image paragraphs into `<figure>` elements even when no caption paragraph/auto caption is present. This is independent of automatic detection.
74
+ - `videoWithoutCaption`, `audioWithoutCaption`, `iframeWithoutCaption`, `iframeTypeBlockquoteWithoutCaption`: wrap the respective media blocks without caption.
75
+
76
+ ### Caption text helpers (delegated to `p7d-markdown-it-p-captions`)
77
+
78
+ Every option below is forwarded verbatim to `p7d-markdown-it-p-captions`, which owns the actual figcaption rendering:
79
+
80
+ - `strongFilename` / `dquoteFilename`: pull out filenames from captions using `**filename**` or `"filename"` syntax and wrap them in `<strong class="f-*-filename">`.
81
+ - `jointSpaceUseHalfWidth`: replace full-width space between Japanese labels and caption body with half-width space.
82
+ - `bLabel` / `strongLabel`: emphasize the label span itself.
83
+ - `removeUnnumberedLabel`: drop the leading “Figure. Etext entirely when no label number is present. Use `removeUnnumberedLabelExceptMarks` to keep specific labels (e.g., `['blockquote']` keeps `Quote. `).
84
+ - `removeMarkNameInCaptionClass`: replace `.f-img-label` / `.f-table-label` with the generic `.f-label`.
85
+ - `wrapCaptionBody`: wrap the non-label caption text in a span element.
86
+ - `hasNumClass`: add a class attribute to label span element if it has a label number.
87
+ - `labelClassFollowsFigure`: mirror the resolved `<figure>` class onto the `figcaption` spans (`f-embed-label`, `f-embed-label-joint`, `f-embed-body`, etc.) when you want captions styled alongside the wrapper.
88
+ - `figureToLabelClassMap`: extend `labelClassFollowsFigure` by mapping specific figure classes (e.g., `f-embed`) to custom caption label classes such as `caption-embed caption-social` for fine-grained control.
89
+ - `labelPrefixMarker`: allow a leading marker before labels (string or array, e.g., `*Figure. ...`). Arrays are limited to two markers; extras are ignored.
90
+
91
+ ### Automatic numbering
92
+
93
+ - `autoLabelNumberSets`: enable numbering per media type. Pass an array such as `['img']`, `['table']`, or `['img', 'table']`.
94
+ - `autoLabelNumber`: shorthand for turning numbering on for both images and tables without passing the array yourself. Provide `autoLabelNumberSets` explicitly (e.g., `['img']`) when you need finer control—the explicit array always wins.
95
+ - Counters start at `1` near the top of the document and increment sequentially per media type. Figures and tables keep independent counters even when mixed together.
96
+ - The counter only advances when a real caption exists (paragraph, auto-detected alt/title, or fallback text). Figures emitted solely because of `oneImageWithoutCaption` stay unnumbered.
97
+ - Manual numbers inside the caption text (e.g., `Figure 5.`) always win. The plugin updates its internal counter so the next automatic number becomes `6`. This applies to captions sourced from paragraphs, auto detection, and fallback captions.
98
+
99
+ ## Basic Usage
100
+
101
+ ```js
102
+ import mdit from 'markdown-it'
103
+ import mditFigureWithPCaption from '@peaceroad/markdown-it-figure-with-p-caption'
104
+ import mditRendererFence from '@peaceroad/markdown-it-renderer-fence' // optional but keeps fences aligned with samples
105
+
106
+ const md = mdit({ html: true, langPrefix: 'language-', })
107
+ .use(mditFigureWithPCaption)
108
+ .use(mditRendererFence)
109
+
110
+ console.log(md.render('Figure. A Cat.\n\n![A cat](cat.jpg)'))
111
+ // <figure class="f-img">
112
+ // <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A Cat.</figcaption>
113
+ // <img src="cat.jpg" alt="A cat">
114
+ // </figure>
115
+ ```
116
+
117
+ ### Basic Recommended Options
118
+
119
+ Auto label numbering for images and tables.
120
+
121
+ ```js
122
+ const figureOption = {
123
+ // Opinionated defaults
124
+ oneImageWithoutCaption: true,
125
+ videoWithoutCaption: true,
126
+ audioWithoutCaption: true,
127
+ iframeWithoutCaption: true,
128
+ iframeTypeBlockquoteWithoutCaption: true,
129
+ removeUnnumberedLabelExceptMarks: ['blockquote'], // keep “Quote. Elabels even when unnumbered
130
+ allIframeTypeFigureClassName: 'f-embed', // apply a uniform class to every iframe-style embed
131
+ autoLabelNumber: true,
132
+
133
+ // If you want to enable auto alt/title captioning fallbacks without caption label.
134
+ //autoAltCaption: true,
135
+ //autoTitleCaption: true,
136
+ }
137
+ ```
138
+
139
+ If there is no label number, the label will also be deleted.
140
+
141
+ ```js
142
+ const figureOption = {
143
+ oneImageWithoutCaption: true,
144
+ videoWithoutCaption: true,
145
+ audioWithoutCaption: true,
146
+ iframeWithoutCaption: true,
147
+ iframeTypeBlockquoteWithoutCaption: true,
148
+ removeUnnumberedLabelExceptMarks: ['blockquote'],
149
+ allIframeTypeFigureClassName: 'f-embed',
150
+ removeUnnumberedLabel: true,
151
+ }
152
+ ```
153
+
154
+ These options can be used as follows:
155
+
156
+ ```
157
+ const md = mdit({ html: true }).use(mditFigureWithPCaption, figureOption)
158
+ ```
159
+
160
+ ## Conversion Examples
161
+
162
+ ### Default before/after caption paragraph detection
163
+
164
+ ~~~
165
+ [Markdown]
166
+ ![A single cat](figure.jpg)
167
+
168
+ [HTML]
169
+ <p><img src="figure.jpg" alt="A single cat"></p>
170
+
171
+ <!-- Above: If oneImageWithoutCaption is true, this img element has wrapped into figure element without caption. -->
172
+
173
+
174
+ [Markdown]
175
+ Figure. A Caption.
176
+
177
+ ![A single cat](figure.jpg)
178
+ [HTML]
179
+ <figure class="f-img">
180
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A Caption.</figcaption>
181
+ <img src="figure.jpg" alt="A single cat">
182
+ </figure>
183
+
184
+
185
+ [Markdown]
186
+ ![A single cat](figure.jpg)
187
+
188
+ Figure. A Caption.
189
+ [HTML]
190
+ <figure class="f-img">
191
+ <img src="figure.jpg" alt="A single cat">
192
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A Caption.</figcaption>
193
+ </figure>
194
+
195
+
196
+ [Markdown]
197
+ Table. A Caption.
198
+
199
+ | Tokyo | Osaka |
200
+ | ----- | ----- |
201
+ | Sushi | Takoyaki |
202
+
203
+ [HTML]
204
+ <p>A paragraph.</p>
205
+ <figure class="f-table">
206
+ <figcaption><span class="f-table-label">Table<span class="f-table-label-joint">.</span></span> A Caption.</figcaption>
207
+ <table>
208
+ <thead>
209
+ <tr>
210
+ <th>Tokyo</th>
211
+ <th>Osaka</th>
212
+ </tr>
213
+ </thead>
214
+ <tbody>
215
+ <tr>
216
+ <td>Sushi</td>
217
+ <td>Takoyaki</td>
218
+ </tr>
219
+ </tbody>
220
+ </table>
221
+ </figure>
222
+
223
+
224
+ [Markdown]
225
+ Code. A Caption.
226
+
227
+ ```js
228
+ console.log('Hello World!');
229
+ ```
230
+
231
+ [HTML]
232
+ <figure class="f-pre-code">
233
+ <figcaption><span class="f-pre-code-label">Code<span class="f-pre-code-label-joint">.</span></span> A Caption.</figcaption>
234
+ <pre><code class="language-js">console.log('Hello World!');
235
+ </code></pre>
236
+ </figure>
237
+
238
+ <!-- Above: class attribute of code element is generated by markdown-it option. -->
239
+
240
+
241
+ [Markdown]
242
+ Source. A Caption.
243
+
244
+ > A quoted paragraph.
245
+
246
+ [HTML]
247
+ <figure class="f-blockquote">
248
+ <figcaption><span class="f-blockquote-label">Source<span class="f-blockquote-label-joint">.</span></span> A Caption.</figcaption>
249
+ <blockquote>
250
+ <p>A quoted paragraph.</p>
251
+ </blockquote>
252
+ </figure>
253
+
254
+
255
+ [Markdown]
256
+ Terminal. A Caption.
257
+
258
+ ```samp
259
+ $ pwd
260
+ /home/user
261
+ ```
262
+
263
+ [HTML]
264
+ <figure class="f-pre-samp">
265
+ <figcaption><span class="f-pre-samp-label">Terminal<span class="f-pre-samp-label-joint">.</span></span> A Caption.</figcaption>
266
+ <pre><samp>$ pwd
267
+ /home/user
268
+ </samp></pre>
269
+ </figure>
270
+
271
+ <!-- Above: When @peaceroad/markdown-it-renderer-fence is used, samp element are generated automatically for `samp` fences. -->
272
+
273
+ [Markdown]
274
+ Video. A mp4.
275
+
276
+ <video controls width="400" height="300">
277
+ <source src="example.mp4" type="video/mp4">
278
+ </video>
279
+
280
+ [HTML]
281
+ <figure class="f-video">
282
+ <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> A mp4.</figcaption>
283
+ <video controls width="400" height="300">
284
+ <source src="example.mp4" type="video/mp4">
285
+ </video>
286
+ </figure>
287
+
288
+
289
+ [Markdown]
290
+ Audio. A narration.
291
+
292
+ <audio controls>
293
+ <source src="example.mp3" type="audio/mpeg">
294
+ </audio>
295
+
296
+ [HTML]
297
+ <figure class="f-audio">
298
+ <figcaption><span class="f-audio-label">Audio<span class="f-audio-label-joint">.</span></span> A narration.</figcaption>
299
+ <audio controls>
300
+ <source src="example.mp3" type="audio/mpeg">
301
+ </audio>
302
+ </figure>
303
+
304
+
305
+ [Markdown]
306
+ Video. A YouTube video.
307
+
308
+ <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
309
+
310
+ [HTML]
311
+ <figure class="f-video">
312
+ <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> A YouTube video.</figcaption>
313
+ <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
314
+ </figure>
315
+
316
+
317
+ [Markdown]
318
+ Figure. Mastodon post.
319
+
320
+ <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
321
+
322
+ [HTML]
323
+ <figure class="f-img">
324
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Mastodon post.</figcaption>
325
+ <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
326
+ </figure>
327
+
328
+
329
+ [Markdown]
330
+ Quote. Mastodon post.
331
+
332
+ <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
333
+
334
+ [HTML]
335
+ <figure class="f-img">
336
+ <figcaption><span class="f-blockquote-label">Quote<span class="f-blockquote-label-joint">.</span></span> X post.</figcaption>
337
+ <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
338
+ </figure>
339
+
340
+
341
+ [Markdown]
342
+ Slide. A Speaker Deck.
343
+
344
+ <iframe src="https://speakerdeck.com/player/XXXXXXXXXXX" width="640" height="360" frameborder="0" allowfullscreen></iframe>
345
+
346
+ [HTML]
347
+ <figure class="f-slide">
348
+ <figcaption><span class="f-slide-label">Slide<span class="f-slide-label-joint">.</span></span> A Speaker Deck.</figcaption>
349
+ <iframe src="https://speakerdeck.com/player/XXXXXXXXXXX" width="640" height="360" frameborder="0" allowfullscreen></iframe>
350
+ </figure>
351
+ ~~~
352
+
353
+ ### Auto alt/title detection
354
+
355
+ ```
356
+ [Markdown]
357
+ ![Figure. A cat.](cat.jpg)
358
+
359
+ [HTML]
360
+ <figure class="f-img">
361
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A cat.</figcaption>
362
+ <img src="cat.jpg" alt="">
363
+ </figure>
364
+
365
+
366
+ [Markdown]
367
+ ![A white cat eats fishs.](cat.jpg "Figure. A cat.")
368
+
369
+ [HTML]
370
+ <figure class="f-img">
371
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A cat.</figcaption>
372
+ <img src="cat.jpg" alt="A white cat eats fishs.">
373
+ </figure>
374
+ ```
375
+
376
+ ### Multiple images
377
+
378
+ ~~~
379
+ [Markdown]
380
+ A paragraph. multipleImages: true. horizontal images only.
381
+
382
+ ![Sitting cat](cat1.jpg) ![Standing cat](cat2.jpg)
383
+
384
+ Figure. Cats.
385
+
386
+ A paragraph.
387
+ [HTML]
388
+ <p>A paragraph. multipleImages: true. horizontal images only</p>
389
+ <figure class="f-img-horizontal">
390
+ <img src="cat1.jpg" alt="Sitting cat"><img src="cat2.jpg" alt="Standing cat">
391
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Cats.</figcaption>
392
+ </figure>
393
+ <p>A paragraph.</p>
394
+
395
+ [Markdown]
396
+ A paragraph. multipleImages: true. vertical images only.
397
+
398
+ Figure. Cats.
399
+
400
+ ![Sitting cat](cat1.jpg)
401
+ ![Standing cat](cat2.jpg)
402
+
403
+ A paragraph.
404
+ [HTML]
405
+ <p>A paragraph. multipleImages: true. vertical images only.</p>
406
+ <figure class="f-img-vertical">
407
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Cats.</figcaption>
408
+ <img src="cat1.jpg" alt="Sitting cat">
409
+ <img src="cat2.jpg" alt="Standing cat">
410
+ </figure>
411
+ <p>A paragraph.</p>
412
+
413
+ [Markdown]
414
+ A paragraph. multipleImages: true.
415
+
416
+ Figure. Cats.
417
+
418
+ ![Sitting cat](cat1.jpg) ![Standing cat](cat2.jpg)
419
+ ![Sleeping cat](cat3.jpg)
420
+
421
+ A paragraph.
422
+ [HTML]
423
+ <p>A paragraph. multipleImages: true.</p>
424
+ <figure class="f-img-multiple">
425
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Cats.</figcaption>
426
+ <img src="cat1.jpg" alt="Sitting cat"><img src="cat2.jpg" alt="Standing cat">
427
+ <img src="cat3.jpg" alt="Sleeping cat">
428
+ </figure>
429
+ <p>A paragraph.</p>
430
+ ~~~
431
+
432
+ ## Option Examples
433
+
434
+ ### Styles
435
+
436
+ This example uses `classPrefix: 'custom'` and leaves `styleProcess: true` so a trailing `{.notice}` block moves onto the `<figure>` wrapper.
437
+
438
+ ```
439
+ [Markdown]
440
+ Figure. Highlighted cat. {.notice}
441
+
442
+ ![Highlighted cat](cat.jpg)
443
+ [HTML]
444
+ <figure class="custom-img notice">
445
+ <figcaption><span class="custom-img-label">Figure<span class="custom-img-label-joint">.</span></span> Highlighted cat.</figcaption>
446
+ <img src="cat.jpg" alt="Highlighted cat">
447
+ </figure>
448
+ ```
449
+
450
+ ### Automatic detection fallbacks
451
+
452
+ `autoCaptionDetection` combined with `autoAltCaption` / `autoTitleCaption` can still generate caption text even when the original alt/title lacks labels. The corresponding attributes are cleared after conversion so the figcaption becomes the canonical source.
453
+
454
+ ```
455
+ [Markdown]
456
+ ![Alt fallback example](bird.jpg)
457
+
458
+ [HTML]
459
+ <figure class="f-img">
460
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Alt fallback example</figcaption>
461
+ <img src="bird.jpg" alt="">
462
+ </figure>
463
+
464
+
465
+ [Markdown]
466
+ ![No caption](fish.jpg "Plain title text")
467
+
468
+ [HTML]
469
+ <figure class="f-img">
470
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Plain title text</figcaption>
471
+ <img src="fish.jpg" alt="No caption">
472
+ </figure>
473
+ ```
474
+
475
+ ### Role helpers
476
+
477
+ Set `roleDocExample: true` to add `role="doc-example"` to code/samp figures.
478
+
479
+ ~~~
480
+ [Markdown]
481
+ ```samp
482
+ $ pwd
483
+ /home/user
484
+ ```
485
+
486
+ [HTML]
487
+ <figure class="f-pre-samp" role="doc-example">
488
+ ...
489
+ </figure>
490
+ ~~~
491
+
492
+ ### Captionless conversion toggles
493
+
494
+ If `oneImageWithoutCaption` is enabled, a single image paragraph will be wrapped with `<figure class="f-img">` even without a caption.
495
+
496
+ ```
497
+ [Markdown]
498
+ ![A single cat](cat.jpg)
499
+
500
+ [HTML]
501
+ <figure class="f-img">
502
+ <img src="cat.jpg" alt="A single cat">
503
+ </figure>
504
+ ```
505
+
506
+ If `videoWithoutCaption` is enabled, an `iframe` pointing to a known video host (such as YouTube or video elements) will be wrapped with `<figure class="f-video">`.
507
+
508
+ ```
509
+ [Markdown]
510
+ <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
511
+
512
+ [HTML]
513
+ <figure class="f-video">
514
+ <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
515
+ </figure>
516
+
517
+
518
+ [Markdown]
519
+ <video controls width="400" height="300">
520
+ <source src="example.mp4" type="video/mp4">
521
+ </video>
522
+ [HTML]
523
+ <figure class="f-video">
524
+ <video controls width="400" height="300">
525
+ <source src="example.mp4" type="video/mp4">
526
+ </video>
527
+ </figure>
528
+ ```
529
+
530
+ When `iframeWithoutCaption` is enabled, iframe elements will be wrapped with `<figure class="f-iframe">`. And if `iframeTypeBlockquoteWithoutCaption` is enabled, blockquote-based embeds (for example, X) will be wrapped with `<figure class="f-img">` (or another configured class).
531
+
532
+ ```
533
+ [Markdown]
534
+ <iframe>
535
+ ...
536
+ </iframe>
537
+
538
+ [HTML]
539
+ <figure class="f-iframe">
540
+ <iframe>
541
+ ...
542
+ </iframe>
543
+ </figure>
544
+ ```
545
+
546
+ ### Iframe-type blockquote class override
547
+
548
+ Set `figureClassThatWrapsIframeTypeBlockquote: 'f-social'` (or any class you prefer) to wrap blockquote-based embeds (for example, X, Mastodon, Bluesky) with that class.
549
+
550
+ ```
551
+ [Markdown]
552
+ Figure. Twitter embed.
553
+
554
+ <blockquote class="twitter-tweet"><p>Embed content</p></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
555
+
556
+ [HTML]
557
+ <figure class="f-social">
558
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Twitter embed.</figcaption>
559
+ <blockquote class="twitter-tweet"><p>Embed content</p></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
560
+ </figure>
561
+ ```
562
+
563
+ ### All iframe/embed class override
564
+
565
+ Set `allIframeTypeFigureClassName: 'f-embed'` (or any class you prefer) to consolidate iframe-like embeds under one class.
566
+
567
+ ```
568
+ [Markdown]
569
+ Video. Custom embed.
570
+
571
+ <iframe width="560" height="315" src="https://example.com/embed/123" title="Custom embed" frameborder="0" allowfullscreen></iframe>
572
+
573
+ [HTML]
574
+ <figure class="f-embed">
575
+ <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> Custom embed.</figcaption>
576
+ <iframe width="560" height="315" src="https://example.com/embed/123" title="Custom embed" frameborder="0" allowfullscreen></iframe>
577
+ </figure>
578
+ ```
579
+
580
+ Need matching caption classes too? Combine this option with `labelClassFollowsFigure` (and optionally `figureToLabelClassMap`) so the `figcaption` spans inherit the embed class you just applied (e.g., `f-embed-label`, `f-embed-label-joint`).
581
+
582
+
583
+ ### Caption markers
584
+
585
+ - `allowLabelPrefixMarkerWithoutLabel`: when `true`, marker-only paragraphs (e.g., `▼Caption`) are treated as captions without labels. If `labelPrefixMarker` is an array, the first entry is used for the previous caption and the second for the next caption. The marker is stripped from output.
586
+
587
+ ```js
588
+ const figureOption = {
589
+ labelPrefixMarker: ['▼', '▲'],
590
+ allowLabelPrefixMarkerWithoutLabel: true,
591
+ }
592
+
593
+ const md = mdit({ html: true }).use(mditFigureWithPCaption, figureOption)
594
+ ```
595
+
596
+ The first marker applies to captions before the figure, the second to captions after it.