@peaceroad/markdown-it-figure-with-p-caption 0.18.0 → 0.20.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.
package/README.md CHANGED
@@ -1,219 +1,114 @@
1
1
  # p7d-markdown-it-figure-with-p-caption
2
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.
3
+ A markdown-it plugin that converts adjacent caption paragraphs and supported
4
+ media blocks into semantic `<figure>` / `<figcaption>` structures.
4
5
 
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
+ Caption-label parsing, numbering grammar, and language catalogs are delegated
7
+ to [`p7d-markdown-it-p-captions`](https://www.npmjs.com/package/p7d-markdown-it-p-captions).
8
+ This plugin owns figure-candidate detection, wrapping, figure classes, and
9
+ optional document/chapter-aware numbering.
6
10
 
7
- Optionally, you can auto-number image and table caption paragraphs starting from the beginning of the document if they only have label names.
11
+ Supported targets include images, tables, code fences, terminal/samp fences,
12
+ blockquotes, video/audio blocks, iframes, social embeds, and slide iframes.
8
13
 
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.
14
+ ## Installation
10
15
 
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 `p7d-markdown-it-p-captions` recognizes as an image caption start (`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` uses locale-aware generated-label defaults from `p7d-markdown-it-p-captions`, so the label text and punctuation stay aligned with the upstream caption language data. A string is treated as a label stem that must be recognized by `p7d-markdown-it-p-captions`; setup throws if it cannot be parsed as an image caption label. This plugin appends the default joint/space unless the string already ends with punctuation such as `.` / `。` / `:`. Empty alt text does not generate a fallback caption.
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
- - Caption detection intentionally skips paragraphs that are the first block inside a list item (`list_item_open` immediately before the paragraph). In practice, `- Figure. ...` followed by an image in the same item is treated as plain text unless you insert another block first.
31
-
32
- ### Table
33
-
34
- - Markdown tables (including those produced by `markdown-it-multimd-table` or similar) convert into `<figure class="f-table">` blocks.
35
- - Caption paragraphs immediately before/after the table become `<figcaption>` element ahead of the `<table>`.
36
-
37
- ### Code block
38
-
39
- - Captions labeled `Code. `, `Terminal. `, etc. wrap the fence in `<figure class="f-pre-code">` / `<figure class="f-pre-samp">`.
40
- - If `roleDocExample: true`, these figures add `role="doc-example"`.
41
-
42
- ### Blockquote
43
-
44
- - Captioned blockquotes (e.g., `Source. A paragraph.` written immediately before or after `> ...`) become `<figure class="f-blockquote">` while keeping the original blockquote intact.
45
-
46
- ### Video & Audio
47
-
48
- - Inline HTML `<video>` and `<audio>` tags are detected as media figures (`<figure class="f-video">` and `<figure class="f-audio">`).
49
- - A caption paragraph labeled `Video. ` / `Audio. ` (or any registered label) is promoted to `<figcaption>` before/after the media so controls remain unobstructed.
50
-
51
- ### Embedded content by iframe
52
-
53
- - Inline HTML `<iframe>` elements become `<figure class="f-video">` when they point to known video hosts (YouTube `www.youtube.com`, `youtube.com`, `www.youtube-nocookie.com`, `youtube-nocookie.com`, Vimeo `player.vimeo.com`).
54
- - `<div>` wrappers are treated as iframe-type embeds only when the same HTML block contains an `<iframe ...>` tag (for example common video wrapper markup).
55
- - 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 list contains one of those provider classes. Extra classes on the same blockquote do not block detection. 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`.
56
- - `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.
57
- - All other iframes fall back to `<figure class="f-iframe">` unless you override the class via `allIframeTypeFigureClassName`.
58
-
59
- ### label span class name
60
-
61
- - 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`.
62
- - With `markdown-it-attrs`, attributes attached to image-only paragraphs (for example `![...](...) {.foo #bar}`) are forwarded to the generated `<figure>`.
63
- - `styleProcess` controls parsing of a trailing `{...}` block from the last text token of an image-only paragraph in this plugin's own scanner. It supports simple `.class`, `#id`, bare attributes, and quoted `key="value with spaces"` / `key='value with spaces'` pairs. It is still a narrow fallback parser, not full `markdown-it-attrs` parity, and attributes already attached to paragraph tokens by `markdown-it-attrs` are still forwarded.
64
- - Attribute forwarding is not sanitization. If you render untrusted Markdown, keep using an HTML sanitizer or a trusted-host policy appropriate for your application; this plugin only decides which already-parsed or narrowly parsed attributes move onto `<figure>`.
65
- - Attributes attached to caption paragraphs stay on the converted `<figcaption>` token after paragraph-to-figcaption conversion.
66
-
67
- ## Behavior Customization
68
-
69
- ### Styles
70
-
71
- - 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.
72
- - `figureClassThatWrapsIframeTypeBlockquote`: override the class used when blockquote-based embeds (Twitter, Mastodon, Bluesky) are wrapped.
73
- - `figureClassThatWrapsSlides`: override the class assigned when a caption paragraph uses the `Slide.` label.
74
- - `classPrefix` (default `f`) controls the CSS namespace for every figure (`f-img`, `f-table`, etc.) so you can align with existing styles.
75
- - Wrapper/class-prefix options are trimmed during setup; whitespace-only values fall back to the default class for that option.
76
-
77
- ### Wrapping without captions
78
-
79
- - `imageOnlyParagraphWithoutCaption`: turn valid image-only paragraphs into `<figure>` elements even when no caption paragraph/auto caption is present. This includes single-image paragraphs and, when `multipleImages` is enabled, multi-image paragraphs that receive classes such as `f-img-horizontal`, `f-img-vertical`, or `f-img-multiple`. This is independent of automatic detection.
80
- - `oneImageWithoutCaption`: legacy alias for `imageOnlyParagraphWithoutCaption`. When both are provided, `imageOnlyParagraphWithoutCaption` wins.
81
- - `videoWithoutCaption`, `audioWithoutCaption`, `iframeWithoutCaption`, `iframeTypeBlockquoteWithoutCaption`: wrap the respective media blocks without caption.
82
-
83
- ### Caption text helpers (delegated to `p7d-markdown-it-p-captions`)
84
-
85
- Every option below is forwarded verbatim to `p7d-markdown-it-p-captions`, which owns the actual figcaption rendering:
86
-
87
- - `strongFilename` / `dquoteFilename`: pull out filenames from captions using `**filename**` or `"filename"` syntax and wrap them in `<strong class="f-*-filename">`.
88
- - `jointSpaceUseHalfWidth`: replace full-width space between Japanese labels and caption body with half-width space.
89
- - `bLabel` / `strongLabel`: emphasize the label span itself.
90
- - `removeUnnumberedLabel`: drop the leading label entirely when no label number is present. Use `removeUnnumberedLabelExceptMarks` to keep specific labels (e.g., `['blockquote']` keeps `Quote. `).
91
- - `removeMarkNameInCaptionClass`: replace `.f-img-label` / `.f-table-label` with the generic `.f-label`.
92
- - `wrapCaptionBody`: wrap the non-label caption text in a span element.
93
- - `hasNumClass`: add a class attribute to label span element if it has a label number.
94
- - `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.
95
- - `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. When this map is provided and `labelClassFollowsFigure` is not set explicitly, figure-following mode is enabled automatically.
96
- - `labelPrefixMarker`: allow a leading marker before labels (string or array, e.g., `*Figure. ...`). Arrays are limited to two markers; extras are ignored.
97
- - `languages`: optional available caption-recognition catalogs delegated to `p7d-markdown-it-p-captions` (default: `['en', 'ja']`). Most users can leave this unset. Set it only when you want to restrict or extend which labels can be recognized (for example English `Figure.` and Japanese `図 `) and which catalogs are available for generated fallback labels. It is separate from the active locale used to choose among those available catalogs.
98
- - Automatic image-label fallback text and punctuation (`Figure. `, `図 `, etc.) are generated from `p7d-markdown-it-p-captions` locale metadata, not from a local hardcoded map in this plugin.
99
- - Generated fallback label tie-break is resolved once per render. Prefer passing the active locale through `env.locale` or `env.preferredLocales`. Compatibility fallbacks are `preferredLanguages`, `env.preferredLanguages`, `env.lang`, and `env.language`. If none of those selects an available catalog, this plugin finally uses a cheap document-script heuristic that skips a leading hyphen-fenced frontmatter block (`---` or longer, spaces allowed before newline), then falls back to the raw `languages` order. This tie-break only affects generated fallback labels; it does not change the caption-recognition dictionaries selected by `languages`. Compatibility note: for generated fallback labels, `env.locale` / `env.preferredLocales` intentionally take precedence over the legacy `preferredLanguages` option so a shared `md` instance can render different documents with different active locales.
16
+ ```console
17
+ npm install markdown-it @peaceroad/markdown-it-figure-with-p-caption
18
+ ```
100
19
 
101
- ### Automatic numbering
20
+ Optional companion plugins:
102
21
 
103
- - `autoLabelNumberSets`: enable numbering per media type. Pass an array such as `['img']`, `['table']`, or `['img', 'table']`.
104
- - `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.
105
- - 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.
106
- - The counter only advances when a real caption exists (paragraph, auto-detected alt/title, or fallback text). Figures emitted solely because of `imageOnlyParagraphWithoutCaption` / `oneImageWithoutCaption` stay unnumbered.
107
- - 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.
22
+ - [`@peaceroad/markdown-it-renderer-image`](https://www.npmjs.com/package/@peaceroad/markdown-it-renderer-image)
23
+ for image width/height rendering.
24
+ - [`@peaceroad/markdown-it-renderer-fence`](https://www.npmjs.com/package/@peaceroad/markdown-it-renderer-fence)
25
+ when terminal fences should render as `<pre><samp>`.
26
+ - [`markdown-it-attrs`](https://www.npmjs.com/package/markdown-it-attrs)
27
+ for broader attribute syntax.
108
28
 
109
- ## Basic Usage
29
+ ## Quick start
110
30
 
111
31
  ```js
112
- import mdit from 'markdown-it'
113
- import mditFigureWithPCaption from '@peaceroad/markdown-it-figure-with-p-caption'
114
- import mditRendererFence from '@peaceroad/markdown-it-renderer-fence' // optional but keeps fences aligned with samples
115
-
116
- const md = mdit({ html: true, langPrefix: 'language-', })
117
- .use(mditFigureWithPCaption)
118
- .use(mditRendererFence)
119
-
120
- console.log(md.render('Figure. A Cat.\n\n![A cat](cat.jpg)'))
121
- // <figure class="f-img">
122
- // <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A Cat.</figcaption>
123
- // <img src="cat.jpg" alt="A cat">
124
- // </figure>
125
- ```
32
+ import markdownIt from 'markdown-it'
33
+ import figureWithCaption from '@peaceroad/markdown-it-figure-with-p-caption'
126
34
 
127
- ### Basic Recommended Options
35
+ const md = markdownIt({ html: true })
36
+ .use(figureWithCaption)
128
37
 
129
- Auto label numbering for images and tables.
130
-
131
- ```js
132
- const figureOption = {
133
- // Opinionated defaults
134
- imageOnlyParagraphWithoutCaption: true,
135
- videoWithoutCaption: true,
136
- audioWithoutCaption: true,
137
- iframeWithoutCaption: true,
138
- iframeTypeBlockquoteWithoutCaption: true,
139
- removeUnnumberedLabelExceptMarks: ['blockquote'], // keep `Quote.` labels even when unnumbered
140
- allIframeTypeFigureClassName: 'f-embed', // apply a uniform class to every iframe-style embed
141
- autoLabelNumber: true,
142
-
143
- // If you want to enable auto alt/title captioning fallbacks without caption label.
144
- //autoAltCaption: true,
145
- //autoTitleCaption: true,
146
- }
38
+ console.log(md.render('Figure. A cat.\n\n![A cat](cat.jpg)'))
147
39
  ```
148
40
 
149
- If there is no label number, the label will also be deleted.
41
+ Output:
150
42
 
151
- ```js
152
- const figureOption = {
153
- imageOnlyParagraphWithoutCaption: true,
154
- videoWithoutCaption: true,
155
- audioWithoutCaption: true,
156
- iframeWithoutCaption: true,
157
- iframeTypeBlockquoteWithoutCaption: true,
158
- removeUnnumberedLabelExceptMarks: ['blockquote'],
159
- allIframeTypeFigureClassName: 'f-embed',
160
- removeUnnumberedLabel: true,
161
- }
43
+ ```html
44
+ <figure class="f-img">
45
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A cat.</figcaption>
46
+ <img src="cat.jpg" alt="A cat">
47
+ </figure>
162
48
  ```
163
49
 
164
- These options can be used as follows:
50
+ Caption paragraphs may appear immediately before or after a supported block.
51
+ Image `alt` / `title` text can also provide a caption when
52
+ `autoCaptionDetection` recognizes a configured label.
165
53
 
166
- ```
167
- const md = mdit({ html: true }).use(mditFigureWithPCaption, figureOption)
168
- ```
54
+ ## What gets converted
169
55
 
170
- ## Conversion Examples
56
+ | Markdown target | Typical caption | Default figure class |
57
+ | --- | --- | --- |
58
+ | Image-only paragraph | `Figure.` / `図` | `f-img` |
59
+ | Table | `Table.` / `表` | `f-table` |
60
+ | Code fence | `Code.` / `Listing.` | `f-pre-code` |
61
+ | Terminal/samp fence | `Terminal.` / `端末` | `f-pre-samp` |
62
+ | Blockquote | `Quote.` / `Source.` | `f-blockquote` |
63
+ | Video block or known video iframe | `Video.` / `動画` | `f-video` |
64
+ | Audio block | `Audio.` / `音声` | `f-audio` |
65
+ | Generic iframe | Any compatible caption label | `f-iframe` |
66
+ | Slide iframe | `Slide.` | `f-slide` |
171
67
 
172
- ### Default before/after caption paragraph detection
173
-
174
- ~~~
175
- [Markdown]
176
- ![A single cat](figure.jpg)
68
+ Exact labels come from the active `p7d-markdown-it-p-captions` language
69
+ catalogs. The default available catalogs are English and Japanese.
177
70
 
178
- [HTML]
179
- <p><img src="figure.jpg" alt="A single cat"></p>
71
+ ## Representative conversions
180
72
 
181
- <!-- Above: If imageOnlyParagraphWithoutCaption (or its legacy alias oneImageWithoutCaption) is true, this img element has wrapped into figure element without caption. -->
73
+ These examples show the default figure classes. Caption span markup is included
74
+ where it helps explain the output; long media contents are abbreviated.
182
75
 
76
+ ### Image
183
77
 
184
- [Markdown]
185
- Figure. A Caption.
78
+ Markdown:
186
79
 
187
- ![A single cat](figure.jpg)
188
- [HTML]
189
- <figure class="f-img">
190
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A Caption.</figcaption>
191
- <img src="figure.jpg" alt="A single cat">
192
- </figure>
80
+ ```md {test id="readme-image"}
81
+ Figure. A cat.
193
82
 
83
+ ![A cat](cat.jpg)
84
+ ```
194
85
 
195
- [Markdown]
196
- ![A single cat](figure.jpg)
86
+ HTML:
197
87
 
198
- Figure. A Caption.
199
- [HTML]
88
+ ```html {test id="readme-image"}
200
89
  <figure class="f-img">
201
- <img src="figure.jpg" alt="A single cat">
202
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A Caption.</figcaption>
90
+ <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A cat.</figcaption>
91
+ <img src="cat.jpg" alt="A cat">
203
92
  </figure>
93
+ ```
204
94
 
95
+ ### Table
96
+
97
+ Markdown:
205
98
 
206
- [Markdown]
207
- Table. A Caption.
99
+ ```md {test id="readme-table"}
100
+ Table. Regional food.
208
101
 
209
102
  | Tokyo | Osaka |
210
- | ----- | ----- |
103
+ | --- | --- |
211
104
  | Sushi | Takoyaki |
105
+ ```
212
106
 
213
- [HTML]
214
- <p>A paragraph.</p>
107
+ HTML:
108
+
109
+ ```html {test id="readme-table"}
215
110
  <figure class="f-table">
216
- <figcaption><span class="f-table-label">Table<span class="f-table-label-joint">.</span></span> A Caption.</figcaption>
111
+ <figcaption><span class="f-table-label">Table<span class="f-table-label-joint">.</span></span> Regional food.</figcaption>
217
112
  <table>
218
113
  <thead>
219
114
  <tr>
@@ -229,378 +124,271 @@ Table. A Caption.
229
124
  </tbody>
230
125
  </table>
231
126
  </figure>
127
+ ```
128
+
129
+ ### Code block
232
130
 
131
+ Markdown:
233
132
 
234
- [Markdown]
235
- Code. A Caption.
133
+ ~~~md {test id="readme-code"}
134
+ Code. Logging example.
236
135
 
237
136
  ```js
238
- console.log('Hello World!');
137
+ console.log('Hello')
239
138
  ```
139
+ ~~~
140
+
141
+ HTML:
240
142
 
241
- [HTML]
143
+ ```html {test id="readme-code"}
242
144
  <figure class="f-pre-code">
243
- <figcaption><span class="f-pre-code-label">Code<span class="f-pre-code-label-joint">.</span></span> A Caption.</figcaption>
244
- <pre><code class="language-js">console.log('Hello World!');
145
+ <figcaption><span class="f-pre-code-label">Code<span class="f-pre-code-label-joint">.</span></span> Logging example.</figcaption>
146
+ <pre><code class="language-js">console.log('Hello')
245
147
  </code></pre>
246
148
  </figure>
149
+ ```
247
150
 
248
- <!-- Above: class attribute of code element is generated by markdown-it option. -->
249
-
250
-
251
- [Markdown]
252
- Source. A Caption.
253
-
254
- > A quoted paragraph.
255
-
256
- [HTML]
257
- <figure class="f-blockquote">
258
- <figcaption><span class="f-blockquote-label">Source<span class="f-blockquote-label-joint">.</span></span> A Caption.</figcaption>
259
- <blockquote>
260
- <p>A quoted paragraph.</p>
261
- </blockquote>
262
- </figure>
151
+ ### Terminal/samp block
263
152
 
153
+ Markdown:
264
154
 
265
- [Markdown]
266
- Terminal. A Caption.
155
+ ~~~md {test id="readme-samp" setup="renderer-fence"}
156
+ Terminal. Current directory.
267
157
 
268
158
  ```samp
269
159
  $ pwd
270
160
  /home/user
271
161
  ```
162
+ ~~~
163
+
164
+ With `@peaceroad/markdown-it-renderer-fence`, the representative output is:
272
165
 
273
- [HTML]
166
+ ```html {test id="readme-samp"}
274
167
  <figure class="f-pre-samp">
275
- <figcaption><span class="f-pre-samp-label">Terminal<span class="f-pre-samp-label-joint">.</span></span> A Caption.</figcaption>
168
+ <figcaption><span class="f-pre-samp-label">Terminal<span class="f-pre-samp-label-joint">.</span></span> Current directory.</figcaption>
276
169
  <pre><samp>$ pwd
277
170
  /home/user
278
171
  </samp></pre>
279
172
  </figure>
173
+ ```
280
174
 
281
- <!-- Above: When @peaceroad/markdown-it-renderer-fence is used, samp element are generated automatically for `samp` fences. -->
282
-
283
- [Markdown]
284
- Video. A mp4.
285
-
286
- <video controls width="400" height="300">
287
- <source src="example.mp4" type="video/mp4">
288
- </video>
289
-
290
- [HTML]
291
- <figure class="f-video">
292
- <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> A mp4.</figcaption>
293
- <video controls width="400" height="300">
294
- <source src="example.mp4" type="video/mp4">
295
- </video>
296
- </figure>
297
-
298
-
299
- [Markdown]
300
- Audio. A narration.
175
+ The figure wrapper and caption classification do not depend on that optional
176
+ renderer; it is used here to show the semantic `<samp>` element.
301
177
 
302
- <audio controls>
303
- <source src="example.mp3" type="audio/mpeg">
304
- </audio>
178
+ ### Video
305
179
 
306
- [HTML]
307
- <figure class="f-audio">
308
- <figcaption><span class="f-audio-label">Audio<span class="f-audio-label-joint">.</span></span> A narration.</figcaption>
309
- <audio controls>
310
- <source src="example.mp3" type="audio/mpeg">
311
- </audio>
312
- </figure>
180
+ Markdown:
313
181
 
182
+ ```md {test id="readme-video"}
183
+ Video. Product demonstration.
314
184
 
315
- [Markdown]
316
- Video. A YouTube video.
185
+ <video controls>
186
+ <source src="demo.mp4" type="video/mp4">
187
+ </video>
188
+ ```
317
189
 
318
- <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
190
+ HTML:
319
191
 
320
- [HTML]
192
+ ```html {test id="readme-video"}
321
193
  <figure class="f-video">
322
- <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> A YouTube video.</figcaption>
323
- <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
324
- </figure>
325
-
326
-
327
- [Markdown]
328
- Figure. Mastodon post.
329
-
330
- <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
331
-
332
- [HTML]
333
- <figure class="f-img">
334
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Mastodon post.</figcaption>
335
- <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
194
+ <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> Product demonstration.</figcaption>
195
+ <video controls>
196
+ <source src="demo.mp4" type="video/mp4">
197
+ </video>
336
198
  </figure>
199
+ ```
337
200
 
201
+ Known YouTube/Vimeo iframe hosts can also use the `f-video` wrapper.
202
+ An unknown iframe with an explicit `Video.` caption uses video numbering but
203
+ keeps the `f-iframe` wrapper.
338
204
 
339
- [Markdown]
340
- Quote. Mastodon post.
341
-
342
- <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
343
-
344
- [HTML]
345
- <figure class="f-img">
346
- <figcaption><span class="f-blockquote-label">Quote<span class="f-blockquote-label-joint">.</span></span> X post.</figcaption>
347
- <blockquote class="mastodon-embed" ...> ...... </blockquote><script async src="https://example.com/embed.js"></script>
348
- </figure>
205
+ ### Slide iframe
349
206
 
207
+ Markdown:
350
208
 
351
- [Markdown]
209
+ ```md {test id="readme-slide"}
352
210
  Slide. A Speaker Deck.
353
211
 
354
- <iframe src="https://speakerdeck.com/player/XXXXXXXXXXX" width="640" height="360" frameborder="0" allowfullscreen></iframe>
355
-
356
- [HTML]
357
- <figure class="f-slide">
358
- <figcaption><span class="f-slide-label">Slide<span class="f-slide-label-joint">.</span></span> A Speaker Deck.</figcaption>
359
- <iframe src="https://speakerdeck.com/player/XXXXXXXXXXX" width="640" height="360" frameborder="0" allowfullscreen></iframe>
360
- </figure>
361
- ~~~
362
-
363
- ### Auto alt/title detection
364
-
212
+ <iframe src="https://speakerdeck.com/player/XXXXXXXXXXX" width="640" height="360" allowfullscreen></iframe>
365
213
  ```
366
- [Markdown]
367
- ![Figure. A cat.](cat.jpg)
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="">
373
- </figure>
374
-
375
-
376
- [Markdown]
377
- ![A white cat eats fishs.](cat.jpg "Figure. A cat.")
378
-
379
- [HTML]
380
- <figure class="f-img">
381
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> A cat.</figcaption>
382
- <img src="cat.jpg" alt="A white cat eats fishs.">
383
- </figure>
384
- ```
385
-
386
- ### Multiple images
387
-
388
- ~~~
389
- [Markdown]
390
- A paragraph. multipleImages: true. horizontal images only.
391
-
392
- ![Sitting cat](cat1.jpg) ![Standing cat](cat2.jpg)
393
-
394
- Figure. Cats.
395
214
 
396
- A paragraph.
397
- [HTML]
398
- <p>A paragraph. multipleImages: true. horizontal images only</p>
399
- <figure class="f-img-horizontal">
400
- <img src="cat1.jpg" alt="Sitting cat"><img src="cat2.jpg" alt="Standing cat">
401
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Cats.</figcaption>
402
- </figure>
403
- <p>A paragraph.</p>
404
-
405
- [Markdown]
406
- A paragraph. multipleImages: true. vertical images only.
407
-
408
- Figure. Cats.
409
-
410
- ![Sitting cat](cat1.jpg)
411
- ![Standing cat](cat2.jpg)
412
-
413
- A paragraph.
414
- [HTML]
415
- <p>A paragraph. multipleImages: true. vertical images only.</p>
416
- <figure class="f-img-vertical">
417
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Cats.</figcaption>
418
- <img src="cat1.jpg" alt="Sitting cat">
419
- <img src="cat2.jpg" alt="Standing cat">
420
- </figure>
421
- <p>A paragraph.</p>
422
-
423
- [Markdown]
424
- A paragraph. multipleImages: true.
425
-
426
- Figure. Cats.
427
-
428
- ![Sitting cat](cat1.jpg) ![Standing cat](cat2.jpg)
429
- ![Sleeping cat](cat3.jpg)
430
-
431
- A paragraph.
432
- [HTML]
433
- <p>A paragraph. multipleImages: true.</p>
434
- <figure class="f-img-multiple">
435
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Cats.</figcaption>
436
- <img src="cat1.jpg" alt="Sitting cat"><img src="cat2.jpg" alt="Standing cat">
437
- <img src="cat3.jpg" alt="Sleeping cat">
438
- </figure>
439
- <p>A paragraph.</p>
440
- ~~~
441
-
442
- ## Option Examples
215
+ HTML:
443
216
 
444
- ### Styles
445
-
446
- This example uses `classPrefix: 'custom'` and leaves `styleProcess: true` so a trailing `{.notice}` block moves onto the `<figure>` wrapper. This fallback only handles the final trailing attrs block on an image-only paragraph; it supports quoted values with spaces, but for broader attrs syntax support, keep using `markdown-it-attrs`.
447
-
448
- ```
449
- [Markdown]
450
- Figure. Highlighted cat.
451
-
452
- ![Highlighted cat](cat.jpg) {.notice}
453
- [HTML]
454
- <figure class="custom-img notice">
455
- <figcaption><span class="custom-img-label">Figure<span class="custom-img-label-joint">.</span></span> Highlighted cat.</figcaption>
456
- <img src="cat.jpg" alt="Highlighted cat">
217
+ ```html {test id="readme-slide"}
218
+ <figure class="f-slide">
219
+ <figcaption><span class="f-slide-label">Slide<span class="f-slide-label-joint">.</span></span> A Speaker Deck.</figcaption>
220
+ <iframe src="https://speakerdeck.com/player/XXXXXXXXXXX" width="640" height="360" allowfullscreen></iframe>
457
221
  </figure>
458
222
  ```
459
223
 
460
- ### Automatic detection fallbacks
461
-
462
- `autoCaptionDetection` combined with `autoAltCaption` / `autoTitleCaption` can still generate caption text even when the original alt/title lacks labels, as long as the alt/title body is non-empty. The corresponding attributes are cleared after conversion so the figcaption becomes the canonical source. When these fallbacks are `true`, the generated label text and punctuation come from `p7d-markdown-it-p-captions` locale metadata rather than a local hardcoded map. When these fallbacks are strings, the string must be a label stem recognized as an image caption label by `p7d-markdown-it-p-captions`; invalid strings fail during plugin setup instead of producing a stray paragraph.
224
+ `figureClassThatWrapsSlides` changes the slide wrapper class.
225
+ `allIframeTypeFigureClassName` takes precedence when all iframe-like embeds
226
+ should share one class.
463
227
 
464
- ```
465
- [Markdown]
466
- ![Alt fallback example](bird.jpg)
228
+ See the [complete conversion examples](docs/examples.md#conversion-examples)
229
+ for before/after captions, multiple images, blockquotes, audio, social embeds,
230
+ and option-specific output.
467
231
 
468
- [HTML]
469
- <figure class="f-img">
470
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Alt fallback example</figcaption>
471
- <img src="bird.jpg" alt="">
472
- </figure>
232
+ ## Recommended options
473
233
 
234
+ The plugin defaults are conservative. The following is a practical,
235
+ opinionated configuration when valid captionless media should also receive
236
+ figure wrappers, iframe-like embeds should share one class, and images/tables
237
+ should be numbered:
474
238
 
475
- [Markdown]
476
- ![No caption](fish.jpg "Plain title text")
239
+ ```js
240
+ const recommendedFigureOptions = {
241
+ imageOnlyParagraphWithoutCaption: true,
242
+ videoWithoutCaption: true,
243
+ audioWithoutCaption: true,
244
+ iframeWithoutCaption: true,
245
+ iframeTypeBlockquoteWithoutCaption: true,
477
246
 
478
- [HTML]
479
- <figure class="f-img">
480
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Plain title text</figcaption>
481
- <img src="fish.jpg" alt="No caption">
482
- </figure>
483
- ```
247
+ // Keep Quote./Source. labels when removeUnnumberedLabel is enabled later.
248
+ removeUnnumberedLabelExceptMarks: ['blockquote'],
484
249
 
485
- ### Role helpers
250
+ // Use one predictable wrapper class for iframe/social-embed figures.
251
+ allIframeTypeFigureClassName: 'f-embed',
486
252
 
487
- Set `roleDocExample: true` to add `role="doc-example"` to code/samp figures.
253
+ // Number image and table captions. Recognized Chapter/Appendix scopes
254
+ // in parsed frontmatter titles or H1 headings are applied automatically.
255
+ autoLabelNumber: true,
256
+ }
488
257
 
489
- ~~~
490
- [Markdown]
491
- ```samp
492
- $ pwd
493
- /home/user
258
+ const md = markdownIt({ html: true })
259
+ .use(figureWithCaption, recommendedFigureOptions)
494
260
  ```
495
261
 
496
- [HTML]
497
- <figure class="f-pre-samp" role="doc-example">
498
- ...
499
- </figure>
500
- ~~~
501
-
502
- ### Captionless conversion toggles
503
-
504
- If `imageOnlyParagraphWithoutCaption` (or the legacy alias `oneImageWithoutCaption`) is enabled, a single image paragraph will be wrapped with `<figure class="f-img">` even without a caption. Multi-image image-only paragraphs can also be wrapped, in which case the normal layout classes such as `f-img-horizontal`, `f-img-vertical`, or `f-img-multiple` are used.
262
+ Optional additions:
505
263
 
506
- ```
507
- [Markdown]
508
- ![A single cat](cat.jpg)
264
+ ```js
265
+ const recommendedFigureOptionsWithFallbacks = {
266
+ ...recommendedFigureOptions,
267
+ // Remove labels such as Figure./Table. when they remain unnumbered.
268
+ removeUnnumberedLabel: true,
509
269
 
510
- [HTML]
511
- <figure class="f-img">
512
- <img src="cat.jpg" alt="A single cat">
513
- </figure>
270
+ // Generate an image label when non-empty alt/title text has no label.
271
+ autoAltCaption: true,
272
+ // autoTitleCaption: true,
273
+ }
514
274
  ```
515
275
 
516
- If `videoWithoutCaption` is enabled, `<video>` elements and iframes pointing to known video hosts (such as `www.youtube.com`, `youtube.com`, `www.youtube-nocookie.com`, or Vimeo) will be wrapped with `<figure class="f-video">`.
517
-
518
- ```
519
- [Markdown]
520
- <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
276
+ `autoAltCaption` and `autoTitleCaption` are disabled by default. When set to
277
+ `true`, generated labels follow p-captions locale metadata. They may also be a
278
+ recognized string label.
521
279
 
522
- [HTML]
523
- <figure class="f-video">
524
- <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/XXXXXXXXXXX" ...></iframe>
525
- </figure>
280
+ See the [complete option reference](docs/reference.md#behavior-customization)
281
+ and [option examples](docs/examples.md#option-examples) for class mapping,
282
+ caption markers, role helpers, captionless conversion, and formatting options.
526
283
 
284
+ ## Automatic numbering
527
285
 
528
- [Markdown]
529
- <video controls width="400" height="300">
530
- <source src="example.mp4" type="video/mp4">
531
- </video>
532
- [HTML]
533
- <figure class="f-video">
534
- <video controls width="400" height="300">
535
- <source src="example.mp4" type="video/mp4">
536
- </video>
537
- </figure>
538
- ```
286
+ Automatic numbering is **disabled by default**.
539
287
 
540
- 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).
288
+ Number image and table captions:
541
289
 
542
- ```
543
- [Markdown]
544
- <iframe>
545
- ...
546
- </iframe>
547
-
548
- [HTML]
549
- <figure class="f-iframe">
550
- <iframe>
551
- ...
552
- </iframe>
553
- </figure>
290
+ ```js
291
+ const md = markdownIt().use(figureWithCaption, {
292
+ autoLabelNumber: true,
293
+ })
554
294
  ```
555
295
 
556
- ### Iframe-type blockquote class override
296
+ Without a recognized chapter or appendix, this generates `Figure 1`,
297
+ `Figure 2`, and so on. Under `# Chapter 1: Introduction`, the same setup
298
+ automatically generates `Figure 1.1`, `Figure 1.2`, and so on. Parsed
299
+ `env.frontmatter.title` values use the same recognition.
557
300
 
558
- Set `figureClassThatWrapsIframeTypeBlockquote: 'f-social'` (or any class you prefer) to wrap blockquote-based embeds (for example, X, Mastodon, Bluesky) with that class.
301
+ The default scoped separator is `.`. Customize the automatic scope only when
302
+ the document structure differs from the defaults:
559
303
 
304
+ ```js
305
+ const md = markdownIt().use(figureWithCaption, {
306
+ autoLabelNumber: true,
307
+ autoLabelNumberPolicy: {
308
+ separator: '-',
309
+ scope: { headingLevels: [2] },
310
+ },
311
+ })
560
312
  ```
561
- [Markdown]
562
- Figure. Twitter embed.
563
-
564
- <blockquote class="twitter-tweet"><p>Embed content</p></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
565
-
566
- [HTML]
567
- <figure class="f-social">
568
- <figcaption><span class="f-img-label">Figure<span class="f-img-label-joint">.</span></span> Twitter embed.</figcaption>
569
- <blockquote class="twitter-tweet"><p>Embed content</p></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
570
- </figure>
571
- ```
572
-
573
- ### All iframe/embed class override
574
313
 
575
- Set `allIframeTypeFigureClassName: 'f-embed'` (or any class you prefer) to consolidate iframe-like embeds under one class.
314
+ This recognizes H2 chapter headings and generates `Figure 1-1`. Use
315
+ `scope: 'document'` when headings and frontmatter titles must not affect
316
+ numbering:
576
317
 
318
+ ```js
319
+ const md = markdownIt().use(figureWithCaption, {
320
+ autoLabelNumber: true,
321
+ autoLabelNumberPolicy: { scope: 'document' },
322
+ })
577
323
  ```
578
- [Markdown]
579
- Video. Custom embed.
580
324
 
581
- <iframe width="560" height="315" src="https://example.com/embed/123" title="Custom embed" frameborder="0" allowfullscreen></iframe>
325
+ Use `autoLabelNumberSets` for additional marks:
582
326
 
583
- [HTML]
584
- <figure class="f-embed">
585
- <figcaption><span class="f-video-label">Video<span class="f-video-label-joint">.</span></span> Custom embed.</figcaption>
586
- <iframe width="560" height="315" src="https://example.com/embed/123" title="Custom embed" frameborder="0" allowfullscreen></iframe>
587
- </figure>
327
+ ```js
328
+ const md = markdownIt().use(figureWithCaption, {
329
+ autoLabelNumberSets: ['img', 'table', 'code', 'samp', 'video'],
330
+ })
588
331
  ```
589
332
 
590
- Need matching caption classes too? Use `labelClassFollowsFigure` (and optionally `figureToLabelClassMap`) so the `figcaption` spans inherit the embed class you just applied (e.g., `f-embed-label`, `f-embed-label-joint`). If `figureToLabelClassMap` is provided, figure-following mode is enabled automatically unless `labelClassFollowsFigure` is set explicitly.
333
+ `autoLabelNumber` is only the image/table shorthand. An explicitly supplied
334
+ `autoLabelNumberSets` always wins, and `autoLabelNumberPolicy` changes
335
+ formatting/scope without enabling marks by itself. Automatic scope defaults to
336
+ parsed frontmatter titles plus top-level H1 headings; customize `sources`,
337
+ `headingLevels`, and `repeatScope` only when needed.
591
338
 
339
+ See the [complete automatic-numbering reference](docs/numbering.md#automatic-numbering)
340
+ for semantic counter series, shared samp labels, manual-number synchronization,
341
+ chapter/appendix recognition, repeat scopes, frontmatter configuration, and
342
+ render-level overrides.
592
343
 
593
- ### Caption markers
344
+ ## Integration API
594
345
 
595
- - `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.
346
+ Source editors and other markdown-it plugins can reuse the same figure-specific
347
+ scope, counter-series, and number-codec semantics without importing the
348
+ renderer walker:
596
349
 
597
350
  ```js
598
- const figureOption = {
599
- labelPrefixMarker: ['▼', '▲'],
600
- allowLabelPrefixMarkerWithoutLabel: true,
601
- }
602
-
603
- const md = mdit({ html: true }).use(mditFigureWithPCaption, figureOption)
351
+ import {
352
+ createFigureCaptionCounterKeyResolver,
353
+ createFigureCaptionNumberCodec,
354
+ createFigureCaptionScopeTimeline,
355
+ normalizeFigureCaptionNumberingPolicy,
356
+ } from '@peaceroad/markdown-it-figure-with-p-caption/caption-numbering.js'
604
357
  ```
605
358
 
606
- The first marker applies to captions before the figure, the second to captions after it.
359
+ See the [caption-numbering integration API reference](docs/numbering.md#caption-numbering-integration-api).
360
+ The public API intentionally does not expose figure-candidate detection,
361
+ wrapping, token mutation, or source editing.
362
+
363
+ ## Important behavior
364
+
365
+ - Repeated `.use(figureWithCaption, ...)` calls on one markdown-it instance use
366
+ first-install-wins behavior. Create separate instances for different
367
+ successful option sets.
368
+ - Tight-list image paragraphs are not wrapped. Loose lists, blockquotes, and
369
+ description lists are supported with documented container guards.
370
+ - `styleProcess` and forwarded markdown-it-attrs attributes are not
371
+ sanitization. Sanitize final rendered HTML when the Markdown input is
372
+ untrusted.
373
+ - Source HTML requires `markdownIt({ html: true })`.
374
+ - The plugin does not parse YAML or install a frontmatter rule. Supply parsed
375
+ metadata through `env.frontmatter`, or configure the documented raw-token
376
+ adapter.
377
+
378
+ ## Documentation
379
+
380
+ The detailed documentation is divided into three files:
381
+
382
+ - [Behavior and options reference](docs/reference.md) — detection, wrapping,
383
+ caption helpers, basic usage, and option contracts.
384
+ - [Automatic numbering and integration API](docs/numbering.md) — numbering
385
+ series, [chapter and appendix scopes](docs/numbering.md#chapter-and-appendix-scopes),
386
+ frontmatter/render overrides, and the
387
+ [public integration API](docs/numbering.md#caption-numbering-integration-api).
388
+ - [Complete conversion and option examples](docs/examples.md) — full
389
+ [conversion examples](docs/examples.md#conversion-examples) and
390
+ [option examples](docs/examples.md#option-examples).
391
+
392
+ ## License
393
+
394
+ MIT