@peaceroad/markdown-it-figure-with-p-caption 0.17.0 → 0.18.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 +18 -15
- package/embeds/detect.js +25 -21
- package/embeds/providers.js +3 -0
- package/index.js +247 -62
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Optionally, you can auto-number image and table caption paragraphs starting from
|
|
|
18
18
|
2. Image `alt` text that `p7d-markdown-it-p-captions` recognizes as an image caption start (`Figure. `, `Figure 1. `, `図 `, `図1 `, etc.).
|
|
19
19
|
3. Image `title` attribute that matches the same labels.
|
|
20
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
|
|
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
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
23
|
- Set `autoCaptionDetection: false` to disable the auto-caption workflow entirely.
|
|
24
24
|
- Multi-image paragraphs are still wrapped as one figure when `multipleImages: true` (default). Layout-specific classes help with styling:
|
|
@@ -41,7 +41,7 @@ Optionally, you can auto-number image and table caption paragraphs starting from
|
|
|
41
41
|
|
|
42
42
|
### Blockquote
|
|
43
43
|
|
|
44
|
-
- Captioned blockquotes (e.g.,
|
|
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
45
|
|
|
46
46
|
### Video & Audio
|
|
47
47
|
|
|
@@ -50,7 +50,7 @@ Optionally, you can auto-number image and table caption paragraphs starting from
|
|
|
50
50
|
|
|
51
51
|
### Embedded content by iframe
|
|
52
52
|
|
|
53
|
-
- Inline HTML `<iframe>` elements become `<figure class="f-video">` when they point to known video hosts (YouTube `youtube-nocookie.com`, Vimeo `player.vimeo.com`).
|
|
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
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
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
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.
|
|
@@ -60,7 +60,8 @@ Optionally, you can auto-number image and table caption paragraphs starting from
|
|
|
60
60
|
|
|
61
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
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 is a narrow fallback parser, not full `markdown-it-attrs` parity, and attributes already attached to paragraph tokens by `markdown-it-attrs` are still forwarded.
|
|
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>`.
|
|
64
65
|
- Attributes attached to caption paragraphs stay on the converted `<figcaption>` token after paragraph-to-figcaption conversion.
|
|
65
66
|
|
|
66
67
|
## Behavior Customization
|
|
@@ -75,7 +76,8 @@ Optionally, you can auto-number image and table caption paragraphs starting from
|
|
|
75
76
|
|
|
76
77
|
### Wrapping without captions
|
|
77
78
|
|
|
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.
|
|
79
81
|
- `videoWithoutCaption`, `audioWithoutCaption`, `iframeWithoutCaption`, `iframeTypeBlockquoteWithoutCaption`: wrap the respective media blocks without caption.
|
|
80
82
|
|
|
81
83
|
### Caption text helpers (delegated to `p7d-markdown-it-p-captions`)
|
|
@@ -92,15 +94,16 @@ Every option below is forwarded verbatim to `p7d-markdown-it-p-captions`, which
|
|
|
92
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.
|
|
93
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.
|
|
94
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.
|
|
95
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.
|
|
96
|
-
-
|
|
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.
|
|
97
100
|
|
|
98
101
|
### Automatic numbering
|
|
99
102
|
|
|
100
103
|
- `autoLabelNumberSets`: enable numbering per media type. Pass an array such as `['img']`, `['table']`, or `['img', 'table']`.
|
|
101
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.
|
|
102
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.
|
|
103
|
-
- 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.
|
|
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.
|
|
104
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.
|
|
105
108
|
|
|
106
109
|
## Basic Usage
|
|
@@ -128,12 +131,12 @@ Auto label numbering for images and tables.
|
|
|
128
131
|
```js
|
|
129
132
|
const figureOption = {
|
|
130
133
|
// Opinionated defaults
|
|
131
|
-
|
|
134
|
+
imageOnlyParagraphWithoutCaption: true,
|
|
132
135
|
videoWithoutCaption: true,
|
|
133
136
|
audioWithoutCaption: true,
|
|
134
137
|
iframeWithoutCaption: true,
|
|
135
138
|
iframeTypeBlockquoteWithoutCaption: true,
|
|
136
|
-
removeUnnumberedLabelExceptMarks: ['blockquote'], // keep
|
|
139
|
+
removeUnnumberedLabelExceptMarks: ['blockquote'], // keep `Quote.` labels even when unnumbered
|
|
137
140
|
allIframeTypeFigureClassName: 'f-embed', // apply a uniform class to every iframe-style embed
|
|
138
141
|
autoLabelNumber: true,
|
|
139
142
|
|
|
@@ -147,7 +150,7 @@ If there is no label number, the label will also be deleted.
|
|
|
147
150
|
|
|
148
151
|
```js
|
|
149
152
|
const figureOption = {
|
|
150
|
-
|
|
153
|
+
imageOnlyParagraphWithoutCaption: true,
|
|
151
154
|
videoWithoutCaption: true,
|
|
152
155
|
audioWithoutCaption: true,
|
|
153
156
|
iframeWithoutCaption: true,
|
|
@@ -175,7 +178,7 @@ const md = mdit({ html: true }).use(mditFigureWithPCaption, figureOption)
|
|
|
175
178
|
[HTML]
|
|
176
179
|
<p><img src="figure.jpg" alt="A single cat"></p>
|
|
177
180
|
|
|
178
|
-
<!-- Above: If oneImageWithoutCaption is true, this img element has wrapped into figure element without caption. -->
|
|
181
|
+
<!-- Above: If imageOnlyParagraphWithoutCaption (or its legacy alias oneImageWithoutCaption) is true, this img element has wrapped into figure element without caption. -->
|
|
179
182
|
|
|
180
183
|
|
|
181
184
|
[Markdown]
|
|
@@ -440,7 +443,7 @@ A paragraph.
|
|
|
440
443
|
|
|
441
444
|
### Styles
|
|
442
445
|
|
|
443
|
-
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; for broader attrs syntax support, keep using `markdown-it-attrs`.
|
|
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`.
|
|
444
447
|
|
|
445
448
|
```
|
|
446
449
|
[Markdown]
|
|
@@ -456,7 +459,7 @@ Figure. Highlighted cat.
|
|
|
456
459
|
|
|
457
460
|
### Automatic detection fallbacks
|
|
458
461
|
|
|
459
|
-
`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.
|
|
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.
|
|
460
463
|
|
|
461
464
|
```
|
|
462
465
|
[Markdown]
|
|
@@ -498,7 +501,7 @@ $ pwd
|
|
|
498
501
|
|
|
499
502
|
### Captionless conversion toggles
|
|
500
503
|
|
|
501
|
-
If `oneImageWithoutCaption` is enabled, a single image paragraph will be wrapped with `<figure class="f-img">` even without a caption.
|
|
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.
|
|
502
505
|
|
|
503
506
|
```
|
|
504
507
|
[Markdown]
|
|
@@ -510,7 +513,7 @@ If `oneImageWithoutCaption` is enabled, a single image paragraph will be wrapped
|
|
|
510
513
|
</figure>
|
|
511
514
|
```
|
|
512
515
|
|
|
513
|
-
If `videoWithoutCaption` is enabled,
|
|
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">`.
|
|
514
517
|
|
|
515
518
|
```
|
|
516
519
|
[Markdown]
|
package/embeds/detect.js
CHANGED
|
@@ -7,25 +7,37 @@ import {
|
|
|
7
7
|
const htmlRegCache = new Map()
|
|
8
8
|
const openingClassAttrReg = /^<[^>]*?\bclass=(?:"([^"]*)"|'([^']*)')/i
|
|
9
9
|
const openingSrcAttrReg = /^<[^>]*?\bsrc=(?:"([^"]*)"|'([^']*)')/i
|
|
10
|
-
const endBlockquoteScriptReg = /<\/blockquote> *<script[^>]*?><\/script>$/
|
|
10
|
+
const endBlockquoteScriptReg = /<\/blockquote> *<script[^>]*?><\/script>$/i
|
|
11
|
+
const targetHtmlHintReg = /<(?:video|audio|iframe|blockquote|div)\b/i
|
|
12
|
+
const blueskyEmbedHintReg = /bluesky-embed/i
|
|
13
|
+
const videoTagHintReg = /<video\b/i
|
|
14
|
+
const audioTagHintReg = /<audio\b/i
|
|
15
|
+
const iframeTagHintReg = /<iframe\b/i
|
|
16
|
+
const blockquoteTagHintReg = /<blockquote\b/i
|
|
17
|
+
const divTagHintReg = /<div\b/i
|
|
11
18
|
const iframeTagReg = /<iframe(?=[\s>])/i
|
|
12
19
|
|
|
13
20
|
const getHtmlReg = (tag) => {
|
|
14
21
|
const cached = htmlRegCache.get(tag)
|
|
15
22
|
if (cached) return cached
|
|
16
23
|
const regexStr = `^<${tag} ?[^>]*?>[\\s\\S]*?<\\/${tag}>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)`
|
|
17
|
-
const reg = new RegExp(regexStr)
|
|
24
|
+
const reg = new RegExp(regexStr, 'i')
|
|
18
25
|
htmlRegCache.set(tag, reg)
|
|
19
26
|
return reg
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
const getHtmlDetectionHints = (content) => {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
const source = typeof content === 'string' ? content : ''
|
|
31
|
+
const hasTargetHtmlHint = targetHtmlHintReg.test(source)
|
|
32
|
+
const hasBlueskyHint = blueskyEmbedHintReg.test(source)
|
|
33
|
+
if (!hasTargetHtmlHint && !hasBlueskyHint) {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
const hasVideoHint = videoTagHintReg.test(source)
|
|
37
|
+
const hasAudioHint = audioTagHintReg.test(source)
|
|
38
|
+
const hasIframeHint = iframeTagHintReg.test(source)
|
|
39
|
+
const hasBlockquoteHint = blockquoteTagHintReg.test(source)
|
|
40
|
+
const hasDivHint = divTagHintReg.test(source)
|
|
29
41
|
return {
|
|
30
42
|
hasBlueskyHint,
|
|
31
43
|
hasVideoHint,
|
|
@@ -33,21 +45,10 @@ const getHtmlDetectionHints = (content) => {
|
|
|
33
45
|
hasIframeHint,
|
|
34
46
|
hasBlockquoteHint,
|
|
35
47
|
hasDivHint,
|
|
36
|
-
hasIframeTag: hasIframeHint || (hasDivHint && iframeTagReg.test(
|
|
48
|
+
hasIframeTag: hasIframeHint || (hasDivHint && iframeTagReg.test(source)),
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
const hasAnyHtmlDetectionHint = (hints) => {
|
|
41
|
-
return !!(
|
|
42
|
-
hints.hasBlueskyHint ||
|
|
43
|
-
hints.hasVideoHint ||
|
|
44
|
-
hints.hasAudioHint ||
|
|
45
|
-
hints.hasIframeHint ||
|
|
46
|
-
hints.hasBlockquoteHint ||
|
|
47
|
-
hints.hasDivHint
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
52
|
const appendHtmlBlockNewlineIfNeeded = (token, hasTag) => {
|
|
52
53
|
if ((hasTag[2] && hasTag[3] !== '\n') || (hasTag[1] !== '\n' && hasTag[2] === undefined)) {
|
|
53
54
|
token.content += '\n'
|
|
@@ -136,13 +137,16 @@ const resolveHtmlWrapWithoutCaption = (matchedTag, result, htmlWrapWithoutCaptio
|
|
|
136
137
|
if (matchedTag === 'blockquote') {
|
|
137
138
|
return !!(result.isIframeTypeBlockquote && htmlWrapWithoutCaption.iframeTypeBlockquote)
|
|
138
139
|
}
|
|
140
|
+
if (matchedTag === 'iframe' && result.isVideoIframe) {
|
|
141
|
+
return !!(htmlWrapWithoutCaption.video || htmlWrapWithoutCaption.iframe)
|
|
142
|
+
}
|
|
139
143
|
return !!htmlWrapWithoutCaption[matchedTag]
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
export const detectHtmlFigureCandidate = (tokens, token, startIndex, htmlWrapWithoutCaption) => {
|
|
143
147
|
if (!token || token.type !== 'html_block') return null
|
|
144
148
|
const hints = getHtmlDetectionHints(token.content)
|
|
145
|
-
if (!
|
|
149
|
+
if (!hints) return null
|
|
146
150
|
|
|
147
151
|
const result = {
|
|
148
152
|
isVideoIframe: false,
|
package/embeds/providers.js
CHANGED
package/index.js
CHANGED
|
@@ -13,9 +13,10 @@ import { detectHtmlFigureCandidate } from './embeds/detect.js'
|
|
|
13
13
|
const imageAttrsReg = /^ *\{(.*?)\} *$/
|
|
14
14
|
const classAttrReg = /^\./
|
|
15
15
|
const idAttrReg = /^#/
|
|
16
|
-
const attrParseReg = /^(.*?)="?(.*)"?$/
|
|
17
16
|
const sampLangReg = /^ *(?:samp|shell|console)(?:(?= )|$)/
|
|
18
17
|
const asciiLabelReg = /^[A-Za-z]/
|
|
18
|
+
const attrNameReg = /^[^\s=]+$/
|
|
19
|
+
const labelTrailingJointReg = /[.\u3002\uff0e::]\s*$/
|
|
19
20
|
const CHECK_TYPE_TOKEN_MAP = {
|
|
20
21
|
table_open: 'table',
|
|
21
22
|
pre_open: 'pre',
|
|
@@ -29,28 +30,47 @@ const normalizeLanguageCode = (value) => {
|
|
|
29
30
|
const separatorIndex = normalized.search(/[-_]/)
|
|
30
31
|
return separatorIndex === -1 ? normalized : normalized.slice(0, separatorIndex)
|
|
31
32
|
}
|
|
33
|
+
const appendAvailableLanguage = (target, lang, availableLanguages) => {
|
|
34
|
+
if (!lang) return false
|
|
35
|
+
if (availableLanguages.indexOf(lang) === -1) return false
|
|
36
|
+
if (target.indexOf(lang) !== -1) return false
|
|
37
|
+
target.push(lang)
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
32
40
|
const normalizePreferredLanguages = (value, availableLanguages) => {
|
|
33
41
|
if (!Array.isArray(availableLanguages) || availableLanguages.length === 0) return []
|
|
34
|
-
const source = typeof value === 'string' ? [value] : (Array.isArray(value) ? value : [])
|
|
35
|
-
if (source.length === 0) return []
|
|
36
|
-
const allowed = new Set(availableLanguages)
|
|
37
42
|
const languages = []
|
|
38
|
-
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
appendAvailableLanguage(languages, normalizeLanguageCode(value), availableLanguages)
|
|
45
|
+
return languages
|
|
46
|
+
}
|
|
47
|
+
const source = Array.isArray(value) ? value : []
|
|
48
|
+
if (source.length === 0) return languages
|
|
39
49
|
for (let i = 0; i < source.length; i++) {
|
|
40
50
|
const lang = normalizeLanguageCode(source[i])
|
|
41
|
-
|
|
42
|
-
seen.add(lang)
|
|
43
|
-
languages.push(lang)
|
|
51
|
+
appendAvailableLanguage(languages, lang, availableLanguages)
|
|
44
52
|
}
|
|
45
53
|
return languages
|
|
46
54
|
}
|
|
47
|
-
const
|
|
48
|
-
if (!
|
|
55
|
+
const prioritizeLanguages = (languages, preferredLanguages) => {
|
|
56
|
+
if (!Array.isArray(languages) || languages.length === 0) return []
|
|
57
|
+
if (typeof preferredLanguages === 'string') {
|
|
58
|
+
if (!preferredLanguages || languages.indexOf(preferredLanguages) === -1) return languages
|
|
59
|
+
if (languages[0] === preferredLanguages) return languages
|
|
60
|
+
const prioritized = [preferredLanguages]
|
|
61
|
+
for (let i = 0; i < languages.length; i++) {
|
|
62
|
+
appendAvailableLanguage(prioritized, languages[i], languages)
|
|
63
|
+
}
|
|
64
|
+
return prioritized
|
|
65
|
+
}
|
|
66
|
+
if (!Array.isArray(preferredLanguages) || preferredLanguages.length === 0) return languages
|
|
49
67
|
const prioritized = []
|
|
50
|
-
|
|
68
|
+
for (let i = 0; i < preferredLanguages.length; i++) {
|
|
69
|
+
appendAvailableLanguage(prioritized, preferredLanguages[i], languages)
|
|
70
|
+
}
|
|
71
|
+
if (prioritized.length === 0) return languages
|
|
51
72
|
for (let i = 0; i < languages.length; i++) {
|
|
52
|
-
|
|
53
|
-
prioritized.push(languages[i])
|
|
73
|
+
appendAvailableLanguage(prioritized, languages[i], languages)
|
|
54
74
|
}
|
|
55
75
|
return prioritized
|
|
56
76
|
}
|
|
@@ -129,34 +149,48 @@ const resolvePreferredLanguagesForState = (state, opt) => {
|
|
|
129
149
|
) ? opt.markRegState.languages : []
|
|
130
150
|
if (availableLanguages.length === 0) return []
|
|
131
151
|
|
|
132
|
-
const explicitPreferred = opt && Array.isArray(opt.preferredLanguages)
|
|
133
|
-
? opt.preferredLanguages
|
|
134
|
-
: []
|
|
135
|
-
if (explicitPreferred.length > 0) return explicitPreferred
|
|
136
|
-
|
|
137
152
|
const optionLanguages = opt && Array.isArray(opt.normalizedOptionLanguages)
|
|
138
153
|
? opt.normalizedOptionLanguages
|
|
139
154
|
: []
|
|
140
155
|
const baseLanguages = optionLanguages.length > 0 ? optionLanguages : availableLanguages
|
|
141
156
|
const env = state && state.env ? state.env : null
|
|
142
|
-
const envPreferred = normalizePreferredLanguages(env && env.preferredLanguages, availableLanguages)
|
|
143
|
-
if (envPreferred.length > 0) return envPreferred
|
|
144
157
|
|
|
145
|
-
const
|
|
158
|
+
const envLocale = normalizeLanguageCode(env && env.locale)
|
|
159
|
+
if (envLocale && baseLanguages.indexOf(envLocale) !== -1) {
|
|
160
|
+
return prioritizeLanguages(baseLanguages, envLocale)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const envPreferredLocales = normalizePreferredLanguages(env && env.preferredLocales, baseLanguages)
|
|
164
|
+
if (envPreferredLocales.length > 0) {
|
|
165
|
+
return prioritizeLanguages(baseLanguages, envPreferredLocales)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const explicitPreferred = opt && Array.isArray(opt.preferredLanguages)
|
|
169
|
+
? opt.preferredLanguages
|
|
170
|
+
: []
|
|
171
|
+
if (explicitPreferred.length > 0) {
|
|
172
|
+
return prioritizeLanguages(baseLanguages, explicitPreferred)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const envPreferred = normalizePreferredLanguages(env && env.preferredLanguages, baseLanguages)
|
|
176
|
+
if (envPreferred.length > 0) {
|
|
177
|
+
return prioritizeLanguages(baseLanguages, envPreferred)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const envLanguage = normalizeLanguageCode(env && (env.preferredLanguage || env.lang || env.language))
|
|
146
181
|
if (envLanguage && baseLanguages.indexOf(envLanguage) !== -1) {
|
|
147
|
-
return
|
|
182
|
+
return prioritizeLanguages(baseLanguages, envLanguage)
|
|
148
183
|
}
|
|
149
184
|
|
|
150
185
|
const detectedLanguage = detectDocumentPrimaryLanguage(state && state.src ? state.src : '', baseLanguages)
|
|
151
186
|
if (detectedLanguage) {
|
|
152
|
-
return
|
|
187
|
+
return prioritizeLanguages(baseLanguages, detectedLanguage)
|
|
153
188
|
}
|
|
154
189
|
return baseLanguages
|
|
155
190
|
}
|
|
156
191
|
const needsPreferredLanguagesResolution = (opt) => {
|
|
157
192
|
if (!opt || !opt.markRegState || !Array.isArray(opt.markRegState.languages)) return false
|
|
158
193
|
if (opt.markRegState.languages.length <= 1) return false
|
|
159
|
-
if (Array.isArray(opt.preferredLanguages) && opt.preferredLanguages.length > 0) return false
|
|
160
194
|
return opt.autoAltCaption === true || opt.autoTitleCaption === true
|
|
161
195
|
}
|
|
162
196
|
const normalizeOptionalClassName = (value) => {
|
|
@@ -190,10 +224,62 @@ const getLabelPrefixMarkerMatch = (inlineToken, markerReg) => {
|
|
|
190
224
|
return match[0]
|
|
191
225
|
}
|
|
192
226
|
|
|
193
|
-
const
|
|
227
|
+
const splitImageAttrParts = (raw) => {
|
|
194
228
|
if (raw === null || raw === undefined) return null
|
|
229
|
+
const parts = []
|
|
230
|
+
let current = ''
|
|
231
|
+
let quote = ''
|
|
232
|
+
let escaped = false
|
|
233
|
+
for (let i = 0; i < raw.length; i++) {
|
|
234
|
+
const ch = raw[i]
|
|
235
|
+
if (quote) {
|
|
236
|
+
current += ch
|
|
237
|
+
if (escaped) {
|
|
238
|
+
escaped = false
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
if (ch === '\\') {
|
|
242
|
+
escaped = true
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
if (ch === quote) {
|
|
246
|
+
quote = ''
|
|
247
|
+
}
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
if (ch === '"' || ch === "'") {
|
|
251
|
+
quote = ch
|
|
252
|
+
current += ch
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
if (ch === ' ') {
|
|
256
|
+
if (current) {
|
|
257
|
+
parts.push(current)
|
|
258
|
+
current = ''
|
|
259
|
+
}
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
current += ch
|
|
263
|
+
}
|
|
264
|
+
if (quote) return null
|
|
265
|
+
if (current) parts.push(current)
|
|
266
|
+
return parts
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const unquoteAttrValue = (value) => {
|
|
270
|
+
if (typeof value !== 'string' || value.length < 2) return value || ''
|
|
271
|
+
const first = value[0]
|
|
272
|
+
const last = value[value.length - 1]
|
|
273
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
274
|
+
return value.slice(1, -1).replace(/\\(["'\\])/g, '$1')
|
|
275
|
+
}
|
|
276
|
+
return value
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const parseImageAttrs = (raw) => {
|
|
280
|
+
const parts = splitImageAttrParts(raw)
|
|
281
|
+
if (!parts || parts.length === 0) return null
|
|
195
282
|
const attrs = []
|
|
196
|
-
const parts = raw.split(/ +/)
|
|
197
283
|
for (let i = 0; i < parts.length; i++) {
|
|
198
284
|
let entry = parts[i]
|
|
199
285
|
if (!entry) continue
|
|
@@ -202,9 +288,15 @@ const parseImageAttrs = (raw) => {
|
|
|
202
288
|
} else if (idAttrReg.test(entry)) {
|
|
203
289
|
entry = entry.replace(idAttrReg, 'id=')
|
|
204
290
|
}
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
207
|
-
|
|
291
|
+
const equalIndex = entry.indexOf('=')
|
|
292
|
+
if (equalIndex === -1) {
|
|
293
|
+
if (!attrNameReg.test(entry)) return null
|
|
294
|
+
attrs.push([entry, ''])
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
const name = entry.slice(0, equalIndex)
|
|
298
|
+
if (!name || !attrNameReg.test(name)) return null
|
|
299
|
+
attrs.push([name, unquoteAttrValue(entry.slice(equalIndex + 1))])
|
|
208
300
|
}
|
|
209
301
|
return attrs
|
|
210
302
|
}
|
|
@@ -299,6 +391,14 @@ const getImageAltText = (token) => {
|
|
|
299
391
|
|
|
300
392
|
const getImageTitleText = (token) => getTokenAttr(token, 'title')
|
|
301
393
|
|
|
394
|
+
const getFallbackStringLabelJoint = (label) => {
|
|
395
|
+
if (!label) return ''
|
|
396
|
+
if (labelTrailingJointReg.test(label)) {
|
|
397
|
+
return asciiLabelReg.test(label) ? ' ' : ''
|
|
398
|
+
}
|
|
399
|
+
return asciiLabelReg.test(label) ? '. ' : ' '
|
|
400
|
+
}
|
|
401
|
+
|
|
302
402
|
const buildCaptionWithFallback = (text, fallbackOption, mark, markRegState, preferredLanguages) => {
|
|
303
403
|
const trimmedText = (text || '').trim()
|
|
304
404
|
if (!fallbackOption) return ''
|
|
@@ -315,7 +415,19 @@ const buildCaptionWithFallback = (text, fallbackOption, mark, markRegState, pref
|
|
|
315
415
|
if (generatedDefaults) {
|
|
316
416
|
return label + (generatedDefaults.joint || '') + (generatedDefaults.space || '') + trimmedText
|
|
317
417
|
}
|
|
318
|
-
return label + (
|
|
418
|
+
return label + getFallbackStringLabelJoint(label) + trimmedText
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const validateFallbackCaptionLabelOption = (optionName, fallbackOption, markRegState) => {
|
|
422
|
+
if (typeof fallbackOption !== 'string') return
|
|
423
|
+
const sampleCaption = buildCaptionWithFallback('caption', fallbackOption, 'img', markRegState, null)
|
|
424
|
+
const analysis = analyzeCaptionStart(sampleCaption, {
|
|
425
|
+
markRegState,
|
|
426
|
+
preferredMark: 'img',
|
|
427
|
+
})
|
|
428
|
+
if (!analysis || analysis.mark !== 'img' || analysis.kind !== 'caption') {
|
|
429
|
+
throw new Error(`${optionName} must be a string label recognized as an image caption by p7d-markdown-it-p-captions: ${fallbackOption}`)
|
|
430
|
+
}
|
|
319
431
|
}
|
|
320
432
|
|
|
321
433
|
const createAutoCaptionParagraph = (captionText, TokenConstructor) => {
|
|
@@ -343,12 +455,18 @@ const getCaptionInlineToken = (tokens, range, caption) => {
|
|
|
343
455
|
}
|
|
344
456
|
|
|
345
457
|
const hasClassName = (classAttr, className) => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
458
|
+
if (!classAttr || !className) return false
|
|
459
|
+
let index = 0
|
|
460
|
+
while (index < classAttr.length) {
|
|
461
|
+
index = classAttr.indexOf(className, index)
|
|
462
|
+
if (index === -1) return false
|
|
463
|
+
const end = index + className.length
|
|
464
|
+
const beforeBoundary = index === 0 || classAttr.charCodeAt(index - 1) <= 0x20
|
|
465
|
+
const afterBoundary = end >= classAttr.length || classAttr.charCodeAt(end) <= 0x20
|
|
466
|
+
if (beforeBoundary && afterBoundary) return true
|
|
467
|
+
index = end
|
|
468
|
+
}
|
|
469
|
+
return false
|
|
352
470
|
}
|
|
353
471
|
|
|
354
472
|
const hasAnyClassName = (classAttr, classNames) => {
|
|
@@ -598,59 +716,117 @@ const changePrevCaptionPosition = (tokens, n, caption, opt) => {
|
|
|
598
716
|
const captionStartToken = tokens[n-3]
|
|
599
717
|
const captionInlineToken = tokens[n-2]
|
|
600
718
|
const captionEndToken = tokens[n-1]
|
|
719
|
+
const figureBaseLevel = getTokenLevel(tokens[n])
|
|
601
720
|
|
|
602
721
|
cleanCaptionTokenAttrs(captionStartToken, caption.name, opt)
|
|
603
722
|
captionStartToken.type = 'figcaption_open'
|
|
604
723
|
captionStartToken.tag = 'figcaption'
|
|
724
|
+
captionStartToken.block = true
|
|
725
|
+
captionStartToken.level = figureBaseLevel + 1
|
|
726
|
+
captionInlineToken.level = figureBaseLevel + 2
|
|
605
727
|
captionEndToken.type = 'figcaption_close'
|
|
606
728
|
captionEndToken.tag = 'figcaption'
|
|
729
|
+
captionEndToken.block = true
|
|
730
|
+
captionEndToken.level = figureBaseLevel + 1
|
|
607
731
|
tokens.splice(n + 2, 0, captionStartToken, captionInlineToken, captionEndToken)
|
|
608
732
|
tokens.splice(n-3, 3)
|
|
609
733
|
return true
|
|
610
734
|
}
|
|
611
735
|
|
|
612
736
|
const changeNextCaptionPosition = (tokens, en, caption, opt) => {
|
|
613
|
-
const captionStartToken = tokens[en+
|
|
614
|
-
const captionInlineToken = tokens[en+
|
|
615
|
-
const captionEndToken = tokens[en+
|
|
737
|
+
const captionStartToken = tokens[en+1]
|
|
738
|
+
const captionInlineToken = tokens[en+2]
|
|
739
|
+
const captionEndToken = tokens[en+3]
|
|
740
|
+
const figureBaseLevel = getTokenLevel(tokens[en])
|
|
616
741
|
cleanCaptionTokenAttrs(captionStartToken, caption.name, opt)
|
|
617
742
|
captionStartToken.type = 'figcaption_open'
|
|
618
743
|
captionStartToken.tag = 'figcaption'
|
|
744
|
+
captionStartToken.block = true
|
|
745
|
+
captionStartToken.level = figureBaseLevel + 1
|
|
746
|
+
captionInlineToken.level = figureBaseLevel + 2
|
|
619
747
|
captionEndToken.type = 'figcaption_close'
|
|
620
748
|
captionEndToken.tag = 'figcaption'
|
|
749
|
+
captionEndToken.block = true
|
|
750
|
+
captionEndToken.level = figureBaseLevel + 1
|
|
621
751
|
tokens.splice(en, 0, captionStartToken, captionInlineToken, captionEndToken)
|
|
622
|
-
tokens.splice(en+
|
|
752
|
+
tokens.splice(en+4, 3)
|
|
623
753
|
return true
|
|
624
754
|
}
|
|
625
755
|
|
|
756
|
+
const getTokenMap = (token) => {
|
|
757
|
+
return token && Array.isArray(token.map) && token.map.length === 2 ? token.map : null
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const findNearestMapInRange = (tokens, start, end, step) => {
|
|
761
|
+
let i = start
|
|
762
|
+
while (step > 0 ? i <= end : i >= end) {
|
|
763
|
+
const map = getTokenMap(tokens[i])
|
|
764
|
+
if (map) return map
|
|
765
|
+
i += step
|
|
766
|
+
}
|
|
767
|
+
return null
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const getRangeMap = (tokens, start, end) => {
|
|
771
|
+
const startMap = getTokenMap(tokens[start]) || findNearestMapInRange(tokens, start, end, 1)
|
|
772
|
+
if (!startMap) return null
|
|
773
|
+
const endMap = getTokenMap(tokens[end]) || findNearestMapInRange(tokens, end, start, -1) || startMap
|
|
774
|
+
const startLine = startMap[0]
|
|
775
|
+
const endLine = Math.max(startMap[1], endMap[1])
|
|
776
|
+
if (typeof startLine !== 'number' || typeof endLine !== 'number' || endLine < startLine) {
|
|
777
|
+
return [startMap[0], startMap[1]]
|
|
778
|
+
}
|
|
779
|
+
return [startLine, endLine]
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const getTokenLevel = (token, fallback = 0) => {
|
|
783
|
+
return token && typeof token.level === 'number' ? token.level : fallback
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const adjustTokenLevels = (tokens, start, end, delta) => {
|
|
787
|
+
if (!delta) return
|
|
788
|
+
for (let i = start; i <= end; i++) {
|
|
789
|
+
const token = tokens[i]
|
|
790
|
+
if (token && typeof token.level === 'number') {
|
|
791
|
+
token.level += delta
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
626
796
|
const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInsteadOfWrap, sp, opt, TokenConstructor) => {
|
|
627
797
|
let n = range.start
|
|
628
798
|
let en = range.end
|
|
799
|
+
const baseLevel = getTokenLevel(tokens[n])
|
|
800
|
+
const childLevel = baseLevel + 1
|
|
629
801
|
const figureStartToken = new TokenConstructor('figure_open', 'figure', 1)
|
|
630
802
|
const figureClassName = sp.figureClassName || resolveFigureClassName(checkTokenTagName, sp, opt)
|
|
631
803
|
figureStartToken.attrSet('class', figureClassName)
|
|
804
|
+
figureStartToken.block = true
|
|
805
|
+
figureStartToken.level = baseLevel
|
|
632
806
|
|
|
633
807
|
if (opt.roleDocExample && (checkTokenTagName === 'pre-code' || checkTokenTagName === 'pre-samp')) {
|
|
634
808
|
figureStartToken.attrSet('role', 'doc-example')
|
|
635
809
|
}
|
|
636
810
|
const figureEndToken = new TokenConstructor('figure_close', 'figure', -1)
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
if (rangeStartMap) {
|
|
644
|
-
figureStartToken.map = [rangeStartMap[0], rangeStartMap[1]]
|
|
645
|
-
}
|
|
646
|
-
if (rangeEndMap) {
|
|
647
|
-
figureEndToken.map = [rangeEndMap[0], rangeEndMap[1]]
|
|
811
|
+
figureEndToken.block = true
|
|
812
|
+
figureEndToken.level = baseLevel
|
|
813
|
+
const rangeMap = getRangeMap(tokens, n, en)
|
|
814
|
+
if (rangeMap) {
|
|
815
|
+
figureStartToken.map = [rangeMap[0], rangeMap[1]]
|
|
816
|
+
figureEndToken.map = [rangeMap[0], rangeMap[1]]
|
|
648
817
|
}
|
|
649
818
|
const createBreakToken = () => {
|
|
650
819
|
const breakToken = new TokenConstructor('text', '', 0)
|
|
651
820
|
breakToken.content = '\n'
|
|
821
|
+
breakToken.level = childLevel
|
|
652
822
|
return breakToken
|
|
653
823
|
}
|
|
824
|
+
const createEmptyTextToken = () => {
|
|
825
|
+
const emptyToken = new TokenConstructor('text', '', 0)
|
|
826
|
+
emptyToken.content = ''
|
|
827
|
+
emptyToken.level = childLevel
|
|
828
|
+
return emptyToken
|
|
829
|
+
}
|
|
654
830
|
if (caption.name === 'img') {
|
|
655
831
|
const joinAttrs = (attrs) => {
|
|
656
832
|
if (!attrs || attrs.length === 0) return
|
|
@@ -665,12 +841,13 @@ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInstea
|
|
|
665
841
|
joinAttrs(tokens[n].attrs)
|
|
666
842
|
}
|
|
667
843
|
if (replaceInsteadOfWrap) {
|
|
668
|
-
tokens.splice(en, 1, createBreakToken(), figureEndToken
|
|
669
|
-
tokens.splice(n, 1, figureStartToken,
|
|
844
|
+
tokens.splice(en, 1, createBreakToken(), figureEndToken)
|
|
845
|
+
tokens.splice(n, 1, figureStartToken, createEmptyTextToken())
|
|
670
846
|
en = en + 2
|
|
671
847
|
} else {
|
|
672
|
-
tokens
|
|
673
|
-
tokens.splice(
|
|
848
|
+
adjustTokenLevels(tokens, n, en, 1)
|
|
849
|
+
tokens.splice(en+1, 0, figureEndToken)
|
|
850
|
+
tokens.splice(n, 0, figureStartToken, createEmptyTextToken())
|
|
674
851
|
en = en + 3
|
|
675
852
|
}
|
|
676
853
|
range.start = n
|
|
@@ -786,7 +963,7 @@ const hasLeadingImageChild = (token) => {
|
|
|
786
963
|
const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
787
964
|
const multipleImagesEnabled = !!opt.multipleImages
|
|
788
965
|
const styleProcessEnabled = !!opt.styleProcess
|
|
789
|
-
const
|
|
966
|
+
const allowImageParagraphWithoutCaption = !!opt.imageOnlyParagraphWithoutCaption
|
|
790
967
|
const children = nextToken.children
|
|
791
968
|
const imageToken = children[0]
|
|
792
969
|
const childrenLength = children.length
|
|
@@ -801,7 +978,7 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
801
978
|
tagName: 'img',
|
|
802
979
|
en: n + 2,
|
|
803
980
|
replaceInsteadOfWrap: true,
|
|
804
|
-
wrapWithoutCaption:
|
|
981
|
+
wrapWithoutCaption: allowImageParagraphWithoutCaption,
|
|
805
982
|
canWrap: true,
|
|
806
983
|
imageToken,
|
|
807
984
|
}
|
|
@@ -825,13 +1002,13 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
825
1002
|
const imageAttrs = rawContent.match(imageAttrsReg)
|
|
826
1003
|
if (imageAttrs) {
|
|
827
1004
|
const parsedAttrs = parseImageAttrs(imageAttrs[1])
|
|
828
|
-
if (parsedAttrs
|
|
1005
|
+
if (parsedAttrs) {
|
|
829
1006
|
for (let i = 0; i < parsedAttrs.length; i++) {
|
|
830
1007
|
sp.attrs.push(parsedAttrs[i])
|
|
831
1008
|
}
|
|
832
1009
|
child.content = ''
|
|
1010
|
+
break
|
|
833
1011
|
}
|
|
834
|
-
break
|
|
835
1012
|
}
|
|
836
1013
|
}
|
|
837
1014
|
if (typeof rawContent === 'string' && rawContent.trim()) {
|
|
@@ -882,7 +1059,7 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
882
1059
|
tagName,
|
|
883
1060
|
en,
|
|
884
1061
|
replaceInsteadOfWrap: true,
|
|
885
|
-
wrapWithoutCaption: isValid &&
|
|
1062
|
+
wrapWithoutCaption: isValid && allowImageParagraphWithoutCaption,
|
|
886
1063
|
canWrap: isValid,
|
|
887
1064
|
imageToken,
|
|
888
1065
|
}
|
|
@@ -1064,14 +1241,15 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1064
1241
|
let opt = {
|
|
1065
1242
|
// Caption languages delegated to p-captions.
|
|
1066
1243
|
languages: ['en', 'ja'],
|
|
1067
|
-
preferredLanguages: null, //
|
|
1244
|
+
preferredLanguages: null, // compatibility tie-break for generated fallback labels; prefer env.locale / env.preferredLocales per render
|
|
1068
1245
|
|
|
1069
1246
|
// --- figure-wrapper behavior ---
|
|
1070
1247
|
classPrefix: 'f',
|
|
1071
1248
|
figureClassThatWrapsIframeTypeBlockquote: null,
|
|
1072
1249
|
figureClassThatWrapsSlides: null,
|
|
1073
1250
|
styleProcess: true,
|
|
1074
|
-
|
|
1251
|
+
imageOnlyParagraphWithoutCaption: false,
|
|
1252
|
+
oneImageWithoutCaption: false, // legacy alias for imageOnlyParagraphWithoutCaption
|
|
1075
1253
|
iframeWithoutCaption: false,
|
|
1076
1254
|
videoWithoutCaption: false,
|
|
1077
1255
|
audioWithoutCaption: false,
|
|
@@ -1110,10 +1288,15 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1110
1288
|
figureToLabelClassMap: null,
|
|
1111
1289
|
}
|
|
1112
1290
|
const hasExplicitAutoLabelNumberSets = option && Object.prototype.hasOwnProperty.call(option, 'autoLabelNumberSets')
|
|
1291
|
+
const hasExplicitImageOnlyParagraphWithoutCaption = option && Object.prototype.hasOwnProperty.call(option, 'imageOnlyParagraphWithoutCaption')
|
|
1113
1292
|
const hasExplicitFigureClassThatWrapsIframeTypeBlockquote = option && Object.prototype.hasOwnProperty.call(option, 'figureClassThatWrapsIframeTypeBlockquote')
|
|
1114
1293
|
const hasExplicitFigureClassThatWrapsSlides = option && Object.prototype.hasOwnProperty.call(option, 'figureClassThatWrapsSlides')
|
|
1115
1294
|
const hasExplicitLabelClassFollowsFigure = option && Object.prototype.hasOwnProperty.call(option, 'labelClassFollowsFigure')
|
|
1116
1295
|
if (option) Object.assign(opt, option)
|
|
1296
|
+
opt.imageOnlyParagraphWithoutCaption = hasExplicitImageOnlyParagraphWithoutCaption
|
|
1297
|
+
? !!opt.imageOnlyParagraphWithoutCaption
|
|
1298
|
+
: !!opt.oneImageWithoutCaption
|
|
1299
|
+
opt.oneImageWithoutCaption = !!opt.oneImageWithoutCaption
|
|
1117
1300
|
if (!hasExplicitLabelClassFollowsFigure && opt.figureToLabelClassMap) {
|
|
1118
1301
|
opt.labelClassFollowsFigure = true
|
|
1119
1302
|
}
|
|
@@ -1124,6 +1307,8 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1124
1307
|
if (opt.preferredLanguages.length === 0) opt.preferredLanguages = null
|
|
1125
1308
|
opt.normalizedOptionLanguages = normalizePreferredLanguages(opt.languages, opt.markRegState.languages)
|
|
1126
1309
|
opt.shouldResolvePreferredLanguages = needsPreferredLanguagesResolution(opt)
|
|
1310
|
+
validateFallbackCaptionLabelOption('autoAltCaption', opt.autoAltCaption, opt.markRegState)
|
|
1311
|
+
validateFallbackCaptionLabelOption('autoTitleCaption', opt.autoTitleCaption, opt.markRegState)
|
|
1127
1312
|
opt.htmlWrapWithoutCaption = {
|
|
1128
1313
|
iframe: !!opt.iframeWithoutCaption,
|
|
1129
1314
|
video: !!opt.videoWithoutCaption,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-it-figure-with-p-caption",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "A markdown-it plugin. For a paragraph with only one image, a table or code block or blockquote, and by writing a caption paragraph immediately before or after, they are converted into the figure element with the figcaption element.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
"url": "https://github.com/peaceroad/p7d-markdown-it-figure-with-p-caption/issues"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.
|
|
24
|
-
"@peaceroad/markdown-it-renderer-fence": "^0.
|
|
25
|
-
"@peaceroad/markdown-it-renderer-image": "^0.
|
|
26
|
-
"@peaceroad/markdown-it-strong-ja": "^0.9.
|
|
23
|
+
"@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.11",
|
|
24
|
+
"@peaceroad/markdown-it-renderer-fence": "^0.7.0",
|
|
25
|
+
"@peaceroad/markdown-it-renderer-image": "^0.15.0",
|
|
26
|
+
"@peaceroad/markdown-it-strong-ja": "^0.9.1",
|
|
27
27
|
"highlight.js": "^11.11.1",
|
|
28
28
|
"markdown-it": "^14.1.0",
|
|
29
29
|
"markdown-it-attrs": "^4.3.1"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"p7d-markdown-it-p-captions": "0.
|
|
32
|
+
"p7d-markdown-it-p-captions": "0.23.0"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"index.js",
|