@peaceroad/markdown-it-figure-with-p-caption 0.16.0 → 0.17.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 +13 -10
- package/embeds/detect.js +178 -0
- package/embeds/providers.js +27 -0
- package/index.js +284 -316
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -15,10 +15,10 @@ Optionally, you can auto-number image and table caption paragraphs starting from
|
|
|
15
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
16
|
- Auto detection runs per image paragraph when `autoCaptionDetection` is `true` (default). The priority is:
|
|
17
17
|
1. Caption paragraphs immediately before or after the image (standard syntax).
|
|
18
|
-
2. Image `alt` text that
|
|
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`
|
|
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 uses that label verbatim. 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:
|
|
@@ -50,9 +50,9 @@ 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
|
|
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`).
|
|
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
|
-
- 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
|
|
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.
|
|
57
57
|
- All other iframes fall back to `<figure class="f-iframe">` unless you override the class via `allIframeTypeFigureClassName`.
|
|
58
58
|
|
|
@@ -60,7 +60,7 @@ 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 trailing `{...}` from
|
|
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.
|
|
64
64
|
- Attributes attached to caption paragraphs stay on the converted `<figcaption>` token after paragraph-to-figcaption conversion.
|
|
65
65
|
|
|
66
66
|
## Behavior Customization
|
|
@@ -71,6 +71,7 @@ Optionally, you can auto-number image and table caption paragraphs starting from
|
|
|
71
71
|
- `figureClassThatWrapsIframeTypeBlockquote`: override the class used when blockquote-based embeds (Twitter, Mastodon, Bluesky) are wrapped.
|
|
72
72
|
- `figureClassThatWrapsSlides`: override the class assigned when a caption paragraph uses the `Slide.` label.
|
|
73
73
|
- `classPrefix` (default `f`) controls the CSS namespace for every figure (`f-img`, `f-table`, etc.) so you can align with existing styles.
|
|
74
|
+
- Wrapper/class-prefix options are trimmed during setup; whitespace-only values fall back to the default class for that option.
|
|
74
75
|
|
|
75
76
|
### Wrapping without captions
|
|
76
77
|
|
|
@@ -84,13 +85,15 @@ Every option below is forwarded verbatim to `p7d-markdown-it-p-captions`, which
|
|
|
84
85
|
- `strongFilename` / `dquoteFilename`: pull out filenames from captions using `**filename**` or `"filename"` syntax and wrap them in `<strong class="f-*-filename">`.
|
|
85
86
|
- `jointSpaceUseHalfWidth`: replace full-width space between Japanese labels and caption body with half-width space.
|
|
86
87
|
- `bLabel` / `strongLabel`: emphasize the label span itself.
|
|
87
|
-
- `removeUnnumberedLabel`: drop the leading
|
|
88
|
+
- `removeUnnumberedLabel`: drop the leading label entirely when no label number is present. Use `removeUnnumberedLabelExceptMarks` to keep specific labels (e.g., `['blockquote']` keeps `Quote. `).
|
|
88
89
|
- `removeMarkNameInCaptionClass`: replace `.f-img-label` / `.f-table-label` with the generic `.f-label`.
|
|
89
90
|
- `wrapCaptionBody`: wrap the non-label caption text in a span element.
|
|
90
91
|
- `hasNumClass`: add a class attribute to label span element if it has a label number.
|
|
91
92
|
- `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.
|
|
92
93
|
- `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.
|
|
93
94
|
- `labelPrefixMarker`: allow a leading marker before labels (string or array, e.g., `*Figure. ...`). Arrays are limited to two markers; extras are ignored.
|
|
95
|
+
- 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
|
+
- `preferredLanguages`: optional tie-break order for generated fallback labels. When omitted, this plugin derives the order once per render from `env.preferredLanguages`, `env.lang` / `env.locale`, then a cheap document-script heuristic that skips a leading hyphen-fenced frontmatter block (`---` or longer, spaces allowed before newline), and finally the raw `languages` order.
|
|
94
97
|
|
|
95
98
|
### Automatic numbering
|
|
96
99
|
|
|
@@ -437,13 +440,13 @@ A paragraph.
|
|
|
437
440
|
|
|
438
441
|
### Styles
|
|
439
442
|
|
|
440
|
-
This example uses `classPrefix: 'custom'` and leaves `styleProcess: true` so a trailing `{.notice}` block moves onto the `<figure>` wrapper.
|
|
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`.
|
|
441
444
|
|
|
442
445
|
```
|
|
443
446
|
[Markdown]
|
|
444
|
-
Figure. Highlighted cat.
|
|
447
|
+
Figure. Highlighted cat.
|
|
445
448
|
|
|
446
|
-

|
|
449
|
+
 {.notice}
|
|
447
450
|
[HTML]
|
|
448
451
|
<figure class="custom-img notice">
|
|
449
452
|
<figcaption><span class="custom-img-label">Figure<span class="custom-img-label-joint">.</span></span> Highlighted cat.</figcaption>
|
|
@@ -453,7 +456,7 @@ Figure. Highlighted cat. {.notice}
|
|
|
453
456
|
|
|
454
457
|
### Automatic detection fallbacks
|
|
455
458
|
|
|
456
|
-
`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.
|
|
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.
|
|
457
460
|
|
|
458
461
|
```
|
|
459
462
|
[Markdown]
|
package/embeds/detect.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BLOCKQUOTE_EMBED_CLASS_NAMES,
|
|
3
|
+
HTML_EMBED_CANDIDATES,
|
|
4
|
+
VIDEO_IFRAME_HOSTS,
|
|
5
|
+
} from './providers.js'
|
|
6
|
+
|
|
7
|
+
const htmlRegCache = new Map()
|
|
8
|
+
const openingClassAttrReg = /^<[^>]*?\bclass=(?:"([^"]*)"|'([^']*)')/i
|
|
9
|
+
const openingSrcAttrReg = /^<[^>]*?\bsrc=(?:"([^"]*)"|'([^']*)')/i
|
|
10
|
+
const endBlockquoteScriptReg = /<\/blockquote> *<script[^>]*?><\/script>$/
|
|
11
|
+
const iframeTagReg = /<iframe(?=[\s>])/i
|
|
12
|
+
|
|
13
|
+
const getHtmlReg = (tag) => {
|
|
14
|
+
const cached = htmlRegCache.get(tag)
|
|
15
|
+
if (cached) return cached
|
|
16
|
+
const regexStr = `^<${tag} ?[^>]*?>[\\s\\S]*?<\\/${tag}>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)`
|
|
17
|
+
const reg = new RegExp(regexStr)
|
|
18
|
+
htmlRegCache.set(tag, reg)
|
|
19
|
+
return reg
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const getHtmlDetectionHints = (content) => {
|
|
23
|
+
const hasBlueskyHint = content.indexOf('bluesky-embed') !== -1
|
|
24
|
+
const hasVideoHint = content.indexOf('<video') !== -1
|
|
25
|
+
const hasAudioHint = content.indexOf('<audio') !== -1
|
|
26
|
+
const hasIframeHint = content.indexOf('<iframe') !== -1
|
|
27
|
+
const hasBlockquoteHint = content.indexOf('<blockquote') !== -1
|
|
28
|
+
const hasDivHint = content.indexOf('<div') !== -1
|
|
29
|
+
return {
|
|
30
|
+
hasBlueskyHint,
|
|
31
|
+
hasVideoHint,
|
|
32
|
+
hasAudioHint,
|
|
33
|
+
hasIframeHint,
|
|
34
|
+
hasBlockquoteHint,
|
|
35
|
+
hasDivHint,
|
|
36
|
+
hasIframeTag: hasIframeHint || (hasDivHint && iframeTagReg.test(content)),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
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
|
+
const appendHtmlBlockNewlineIfNeeded = (token, hasTag) => {
|
|
52
|
+
if ((hasTag[2] && hasTag[3] !== '\n') || (hasTag[1] !== '\n' && hasTag[2] === undefined)) {
|
|
53
|
+
token.content += '\n'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const consumeBlockquoteEmbedScript = (tokens, token, startIndex) => {
|
|
58
|
+
let addedContent = ''
|
|
59
|
+
let i = startIndex + 1
|
|
60
|
+
while (i < tokens.length) {
|
|
61
|
+
const nextToken = tokens[i]
|
|
62
|
+
if (nextToken.type === 'inline' && endBlockquoteScriptReg.test(nextToken.content)) {
|
|
63
|
+
addedContent += nextToken.content + '\n'
|
|
64
|
+
if (tokens[i + 1] && tokens[i + 1].type === 'paragraph_close') {
|
|
65
|
+
tokens.splice(i + 1, 1)
|
|
66
|
+
}
|
|
67
|
+
nextToken.content = ''
|
|
68
|
+
if (nextToken.children) {
|
|
69
|
+
for (let j = 0; j < nextToken.children.length; j++) {
|
|
70
|
+
nextToken.children[j].content = ''
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
if (nextToken.type === 'paragraph_open') {
|
|
76
|
+
addedContent += '\n'
|
|
77
|
+
tokens.splice(i, 1)
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
i++
|
|
81
|
+
}
|
|
82
|
+
token.content += addedContent
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const getOpeningAttrValue = (content, reg) => {
|
|
86
|
+
if (typeof content !== 'string' || content.charCodeAt(0) !== 0x3c) return ''
|
|
87
|
+
const match = content.match(reg)
|
|
88
|
+
if (!match) return ''
|
|
89
|
+
return match[1] || match[2] || ''
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const hasKnownBlockquoteEmbedClass = (content) => {
|
|
93
|
+
const classAttr = getOpeningAttrValue(content, openingClassAttrReg)
|
|
94
|
+
if (!classAttr) return false
|
|
95
|
+
let start = 0
|
|
96
|
+
while (start < classAttr.length) {
|
|
97
|
+
while (start < classAttr.length && classAttr.charCodeAt(start) <= 0x20) start++
|
|
98
|
+
if (start >= classAttr.length) break
|
|
99
|
+
let end = start + 1
|
|
100
|
+
while (end < classAttr.length && classAttr.charCodeAt(end) > 0x20) end++
|
|
101
|
+
if (BLOCKQUOTE_EMBED_CLASS_NAMES.has(classAttr.slice(start, end))) return true
|
|
102
|
+
start = end + 1
|
|
103
|
+
}
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const isKnownVideoIframe = (content) => {
|
|
108
|
+
const src = getOpeningAttrValue(content, openingSrcAttrReg)
|
|
109
|
+
if (!src || src.slice(0, 8).toLowerCase() !== 'https://') return false
|
|
110
|
+
const slashIndex = src.indexOf('/', 8)
|
|
111
|
+
const host = (slashIndex === -1 ? src.slice(8) : src.slice(8, slashIndex)).toLowerCase()
|
|
112
|
+
return VIDEO_IFRAME_HOSTS.has(host)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const detectHtmlTagCandidate = (tokens, token, startIndex, detector, hints, result) => {
|
|
116
|
+
if (detector.requiresIframeTag && !hints.hasIframeTag) return ''
|
|
117
|
+
const hasTagHint = !!(detector.hintKey && hints[detector.hintKey])
|
|
118
|
+
const allowBlueskyFallback = detector.candidate === 'blockquote' && hints.hasBlueskyHint
|
|
119
|
+
if (!hasTagHint && !allowBlueskyFallback) return ''
|
|
120
|
+
const hasTag = hasTagHint ? token.content.match(getHtmlReg(detector.lookupTag)) : null
|
|
121
|
+
const isBlueskyFallback = detector.candidate === 'blockquote' && !hasTag && hints.hasBlueskyHint
|
|
122
|
+
if (!hasTag && !isBlueskyFallback) return ''
|
|
123
|
+
if (hasTag) {
|
|
124
|
+
appendHtmlBlockNewlineIfNeeded(token, hasTag)
|
|
125
|
+
if (detector.treatAsVideoIframe) {
|
|
126
|
+
result.isVideoIframe = true
|
|
127
|
+
}
|
|
128
|
+
return detector.matchedTag || detector.candidate
|
|
129
|
+
}
|
|
130
|
+
consumeBlockquoteEmbedScript(tokens, token, startIndex)
|
|
131
|
+
return 'blockquote'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const resolveHtmlWrapWithoutCaption = (matchedTag, result, htmlWrapWithoutCaption) => {
|
|
135
|
+
if (!htmlWrapWithoutCaption) return false
|
|
136
|
+
if (matchedTag === 'blockquote') {
|
|
137
|
+
return !!(result.isIframeTypeBlockquote && htmlWrapWithoutCaption.iframeTypeBlockquote)
|
|
138
|
+
}
|
|
139
|
+
return !!htmlWrapWithoutCaption[matchedTag]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const detectHtmlFigureCandidate = (tokens, token, startIndex, htmlWrapWithoutCaption) => {
|
|
143
|
+
if (!token || token.type !== 'html_block') return null
|
|
144
|
+
const hints = getHtmlDetectionHints(token.content)
|
|
145
|
+
if (!hasAnyHtmlDetectionHint(hints)) return null
|
|
146
|
+
|
|
147
|
+
const result = {
|
|
148
|
+
isVideoIframe: false,
|
|
149
|
+
isIframeTypeBlockquote: false,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let matchedTag = ''
|
|
153
|
+
for (let i = 0; i < HTML_EMBED_CANDIDATES.length; i++) {
|
|
154
|
+
matchedTag = detectHtmlTagCandidate(tokens, token, startIndex, HTML_EMBED_CANDIDATES[i], hints, result)
|
|
155
|
+
if (matchedTag) break
|
|
156
|
+
}
|
|
157
|
+
if (!matchedTag) return null
|
|
158
|
+
|
|
159
|
+
if (matchedTag === 'blockquote') {
|
|
160
|
+
if (!hasKnownBlockquoteEmbedClass(token.content)) return null
|
|
161
|
+
result.isIframeTypeBlockquote = true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (matchedTag === 'iframe' && isKnownVideoIframe(token.content)) {
|
|
165
|
+
result.isVideoIframe = true
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
type: 'html',
|
|
170
|
+
tagName: matchedTag,
|
|
171
|
+
en: startIndex,
|
|
172
|
+
replaceInsteadOfWrap: false,
|
|
173
|
+
wrapWithoutCaption: resolveHtmlWrapWithoutCaption(matchedTag, result, htmlWrapWithoutCaption),
|
|
174
|
+
canWrap: true,
|
|
175
|
+
isVideoIframe: result.isVideoIframe,
|
|
176
|
+
isIframeTypeBlockquote: result.isIframeTypeBlockquote,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const HTML_EMBED_CANDIDATES = Object.freeze([
|
|
2
|
+
{ candidate: 'video', lookupTag: 'video', hintKey: 'hasVideoHint' },
|
|
3
|
+
{ candidate: 'audio', lookupTag: 'audio', hintKey: 'hasAudioHint' },
|
|
4
|
+
{ candidate: 'iframe', lookupTag: 'iframe', hintKey: 'hasIframeHint' },
|
|
5
|
+
{ candidate: 'blockquote', lookupTag: 'blockquote', hintKey: 'hasBlockquoteHint' },
|
|
6
|
+
{
|
|
7
|
+
candidate: 'div',
|
|
8
|
+
lookupTag: 'div',
|
|
9
|
+
hintKey: 'hasDivHint',
|
|
10
|
+
requiresIframeTag: true,
|
|
11
|
+
matchedTag: 'iframe',
|
|
12
|
+
treatAsVideoIframe: true,
|
|
13
|
+
},
|
|
14
|
+
])
|
|
15
|
+
|
|
16
|
+
export const BLOCKQUOTE_EMBED_CLASS_NAMES = new Set([
|
|
17
|
+
'twitter-tweet',
|
|
18
|
+
'instagram-media',
|
|
19
|
+
'text-post-media',
|
|
20
|
+
'bluesky-embed',
|
|
21
|
+
'mastodon-embed',
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
export const VIDEO_IFRAME_HOSTS = new Set([
|
|
25
|
+
'www.youtube-nocookie.com',
|
|
26
|
+
'player.vimeo.com',
|
|
27
|
+
])
|
package/index.js
CHANGED
|
@@ -1,62 +1,176 @@
|
|
|
1
1
|
import {
|
|
2
|
+
analyzeCaptionStart,
|
|
3
|
+
buildLabelClassLookup,
|
|
4
|
+
buildLabelPrefixMarkerRegFromMarkers,
|
|
5
|
+
getGeneratedLabelDefaults,
|
|
6
|
+
normalizeLabelPrefixMarkers,
|
|
2
7
|
setCaptionParagraph,
|
|
3
8
|
getMarkRegStateForLanguages,
|
|
9
|
+
stripLabelPrefixMarker,
|
|
4
10
|
} from 'p7d-markdown-it-p-captions'
|
|
11
|
+
import { detectHtmlFigureCandidate } from './embeds/detect.js'
|
|
5
12
|
|
|
6
|
-
const htmlRegCache = new Map()
|
|
7
|
-
const blueskyEmbedReg = /^<blockquote class="bluesky-embed"[^]*?>[\s\S]*?$/
|
|
8
|
-
const videoIframeReg = /^<[^>]*? src="https:\/\/(?:www.youtube-nocookie.com|player.vimeo.com)\//i
|
|
9
|
-
const classNameReg = /^<[^>]*? class="(twitter-tweet|instagram-media|text-post-media|bluesky-embed|mastodon-embed)"/
|
|
10
13
|
const imageAttrsReg = /^ *\{(.*?)\} *$/
|
|
11
14
|
const classAttrReg = /^\./
|
|
12
15
|
const idAttrReg = /^#/
|
|
13
16
|
const attrParseReg = /^(.*?)="?(.*)"?$/
|
|
14
17
|
const sampLangReg = /^ *(?:samp|shell|console)(?:(?= )|$)/
|
|
15
|
-
const endBlockquoteScriptReg = /<\/blockquote> *<script[^>]*?><\/script>$/
|
|
16
|
-
const iframeTagReg = /<iframe(?=[\s>])/i
|
|
17
18
|
const asciiLabelReg = /^[A-Za-z]/
|
|
18
19
|
const CHECK_TYPE_TOKEN_MAP = {
|
|
19
20
|
table_open: 'table',
|
|
20
21
|
pre_open: 'pre',
|
|
21
22
|
blockquote_open: 'blockquote',
|
|
22
23
|
}
|
|
23
|
-
const HTML_TAG_CANDIDATES = ['video', 'audio', 'iframe', 'blockquote', 'div']
|
|
24
|
-
const fallbackLabelDefaults = {
|
|
25
|
-
img: { en: 'Figure', ja: '図' },
|
|
26
|
-
table: { en: 'Table', ja: '表' },
|
|
27
|
-
}
|
|
28
|
-
|
|
29
24
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
const normalizeLanguageCode = (value) => {
|
|
26
|
+
if (value === null || value === undefined) return ''
|
|
27
|
+
const normalized = String(value).trim().toLowerCase()
|
|
28
|
+
if (!normalized) return ''
|
|
29
|
+
const separatorIndex = normalized.search(/[-_]/)
|
|
30
|
+
return separatorIndex === -1 ? normalized : normalized.slice(0, separatorIndex)
|
|
31
|
+
}
|
|
32
|
+
const normalizePreferredLanguages = (value, availableLanguages) => {
|
|
33
|
+
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
|
+
const languages = []
|
|
34
38
|
const seen = new Set()
|
|
35
|
-
for (let i = 0; i <
|
|
36
|
-
const lang =
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
seen.add(trimmed)
|
|
41
|
-
normalized.push(trimmed)
|
|
39
|
+
for (let i = 0; i < source.length; i++) {
|
|
40
|
+
const lang = normalizeLanguageCode(source[i])
|
|
41
|
+
if (!lang || seen.has(lang) || !allowed.has(lang)) continue
|
|
42
|
+
seen.add(lang)
|
|
43
|
+
languages.push(lang)
|
|
42
44
|
}
|
|
43
|
-
|
|
44
|
-
return normalized
|
|
45
|
+
return languages
|
|
45
46
|
}
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
|
|
47
|
+
const prioritizeLanguage = (languages, preferredLanguage) => {
|
|
48
|
+
if (!preferredLanguage || languages.length === 0) return languages.slice()
|
|
49
|
+
const prioritized = []
|
|
50
|
+
prioritized.push(preferredLanguage)
|
|
51
|
+
for (let i = 0; i < languages.length; i++) {
|
|
52
|
+
if (languages[i] === preferredLanguage) continue
|
|
53
|
+
prioritized.push(languages[i])
|
|
54
|
+
}
|
|
55
|
+
return prioritized
|
|
56
|
+
}
|
|
57
|
+
const isAsciiAlphaCode = (code) => {
|
|
58
|
+
return (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a)
|
|
59
|
+
}
|
|
60
|
+
const isJapaneseCharCode = (code) => {
|
|
61
|
+
return (
|
|
62
|
+
(code >= 0x3040 && code <= 0x30ff) ||
|
|
63
|
+
(code >= 0x31f0 && code <= 0x31ff) ||
|
|
64
|
+
(code >= 0x4e00 && code <= 0x9fff) ||
|
|
65
|
+
(code >= 0xff66 && code <= 0xff9f)
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
const isHyphenFenceLine = (src, lineStart) => {
|
|
69
|
+
if (typeof src !== 'string' || lineStart < 0 || lineStart >= src.length) return 0
|
|
70
|
+
let index = lineStart
|
|
71
|
+
let hyphenCount = 0
|
|
72
|
+
while (index < src.length && src.charCodeAt(index) === 0x2d) {
|
|
73
|
+
hyphenCount++
|
|
74
|
+
index++
|
|
75
|
+
}
|
|
76
|
+
if (hyphenCount < 3) return 0
|
|
77
|
+
while (index < src.length && src.charCodeAt(index) === 0x20) {
|
|
78
|
+
index++
|
|
79
|
+
}
|
|
80
|
+
if (index >= src.length || src.charCodeAt(index) !== 0x0a) return 0
|
|
81
|
+
return hyphenCount
|
|
82
|
+
}
|
|
83
|
+
const skipLeadingFrontmatter = (src) => {
|
|
84
|
+
if (typeof src !== 'string' || isHyphenFenceLine(src, 0) === 0) return src
|
|
85
|
+
let lineStart = src.indexOf('\n')
|
|
86
|
+
if (lineStart === -1) return src
|
|
87
|
+
lineStart++
|
|
88
|
+
while (lineStart < src.length) {
|
|
89
|
+
if (isHyphenFenceLine(src, lineStart) > 0) {
|
|
90
|
+
const nextLineStart = src.indexOf('\n', lineStart)
|
|
91
|
+
if (nextLineStart === -1) return ''
|
|
92
|
+
return src.slice(nextLineStart + 1)
|
|
93
|
+
}
|
|
94
|
+
const nextLineStart = src.indexOf('\n', lineStart)
|
|
95
|
+
if (nextLineStart === -1) break
|
|
96
|
+
lineStart = nextLineStart + 1
|
|
49
97
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
98
|
+
return src
|
|
99
|
+
}
|
|
100
|
+
const detectDocumentPrimaryLanguage = (src, availableLanguages) => {
|
|
101
|
+
if (!src || availableLanguages.indexOf('ja') === -1) return ''
|
|
102
|
+
const body = skipLeadingFrontmatter(src)
|
|
103
|
+
const limit = Math.min(body.length, 8192)
|
|
104
|
+
let japaneseCount = 0
|
|
105
|
+
let asciiAlphaCount = 0
|
|
106
|
+
for (let i = 0; i < limit; i++) {
|
|
107
|
+
const code = body.charCodeAt(i)
|
|
108
|
+
if (isJapaneseCharCode(code)) {
|
|
109
|
+
japaneseCount++
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
if (isAsciiAlphaCode(code)) {
|
|
113
|
+
asciiAlphaCount++
|
|
114
|
+
}
|
|
53
115
|
}
|
|
54
|
-
return
|
|
116
|
+
if (japaneseCount === 0) return ''
|
|
117
|
+
if (asciiAlphaCount === 0) return 'ja'
|
|
118
|
+
return japaneseCount * 2 >= asciiAlphaCount ? 'ja' : ''
|
|
119
|
+
}
|
|
120
|
+
const sourceMayNeedPreferredLanguages = (state) => {
|
|
121
|
+
const src = state && typeof state.src === 'string' ? state.src : ''
|
|
122
|
+
return src.indexOf('![') !== -1
|
|
123
|
+
}
|
|
124
|
+
const resolvePreferredLanguagesForState = (state, opt) => {
|
|
125
|
+
const availableLanguages = (
|
|
126
|
+
opt &&
|
|
127
|
+
opt.markRegState &&
|
|
128
|
+
Array.isArray(opt.markRegState.languages)
|
|
129
|
+
) ? opt.markRegState.languages : []
|
|
130
|
+
if (availableLanguages.length === 0) return []
|
|
131
|
+
|
|
132
|
+
const explicitPreferred = opt && Array.isArray(opt.preferredLanguages)
|
|
133
|
+
? opt.preferredLanguages
|
|
134
|
+
: []
|
|
135
|
+
if (explicitPreferred.length > 0) return explicitPreferred
|
|
136
|
+
|
|
137
|
+
const optionLanguages = opt && Array.isArray(opt.normalizedOptionLanguages)
|
|
138
|
+
? opt.normalizedOptionLanguages
|
|
139
|
+
: []
|
|
140
|
+
const baseLanguages = optionLanguages.length > 0 ? optionLanguages : availableLanguages
|
|
141
|
+
const env = state && state.env ? state.env : null
|
|
142
|
+
const envPreferred = normalizePreferredLanguages(env && env.preferredLanguages, availableLanguages)
|
|
143
|
+
if (envPreferred.length > 0) return envPreferred
|
|
144
|
+
|
|
145
|
+
const envLanguage = normalizeLanguageCode(env && (env.preferredLanguage || env.lang || env.language || env.locale))
|
|
146
|
+
if (envLanguage && baseLanguages.indexOf(envLanguage) !== -1) {
|
|
147
|
+
return prioritizeLanguage(baseLanguages, envLanguage)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const detectedLanguage = detectDocumentPrimaryLanguage(state && state.src ? state.src : '', baseLanguages)
|
|
151
|
+
if (detectedLanguage) {
|
|
152
|
+
return prioritizeLanguage(baseLanguages, detectedLanguage)
|
|
153
|
+
}
|
|
154
|
+
return baseLanguages
|
|
155
|
+
}
|
|
156
|
+
const needsPreferredLanguagesResolution = (opt) => {
|
|
157
|
+
if (!opt || !opt.markRegState || !Array.isArray(opt.markRegState.languages)) return false
|
|
158
|
+
if (opt.markRegState.languages.length <= 1) return false
|
|
159
|
+
if (Array.isArray(opt.preferredLanguages) && opt.preferredLanguages.length > 0) return false
|
|
160
|
+
return opt.autoAltCaption === true || opt.autoTitleCaption === true
|
|
161
|
+
}
|
|
162
|
+
const normalizeOptionalClassName = (value) => {
|
|
163
|
+
if (value === null || value === undefined) return ''
|
|
164
|
+
const normalized = String(value).trim()
|
|
165
|
+
return normalized || ''
|
|
166
|
+
}
|
|
167
|
+
const buildClassPrefix = (value) => {
|
|
168
|
+
const normalized = normalizeOptionalClassName(value)
|
|
169
|
+
return normalized ? normalized + '-' : ''
|
|
55
170
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return new RegExp('^(?:' + pattern + ')(?:[ \\t ]+)?')
|
|
171
|
+
const normalizeClassOptionWithFallback = (value, fallbackValue) => {
|
|
172
|
+
const normalized = normalizeOptionalClassName(value)
|
|
173
|
+
return normalized || fallbackValue
|
|
60
174
|
}
|
|
61
175
|
const resolveLabelPrefixMarkerPair = (markers) => {
|
|
62
176
|
if (!markers || markers.length === 0) return { prev: [], next: [] }
|
|
@@ -65,26 +179,6 @@ const resolveLabelPrefixMarkerPair = (markers) => {
|
|
|
65
179
|
}
|
|
66
180
|
return { prev: [markers[0]], next: [markers[1]] }
|
|
67
181
|
}
|
|
68
|
-
const stripLeadingPrefix = (text, prefix) => {
|
|
69
|
-
if (typeof text !== 'string' || !text || !prefix) return text
|
|
70
|
-
if (text.startsWith(prefix)) return text.slice(prefix.length)
|
|
71
|
-
return text
|
|
72
|
-
}
|
|
73
|
-
const stripLabelPrefixMarkerFromInline = (inlineToken, markerText) => {
|
|
74
|
-
if (!inlineToken || !markerText) return
|
|
75
|
-
if (typeof inlineToken.content === 'string') {
|
|
76
|
-
inlineToken.content = stripLeadingPrefix(inlineToken.content, markerText)
|
|
77
|
-
}
|
|
78
|
-
if (inlineToken.children && inlineToken.children.length) {
|
|
79
|
-
for (let i = 0; i < inlineToken.children.length; i++) {
|
|
80
|
-
const child = inlineToken.children[i]
|
|
81
|
-
if (child && child.type === 'text' && typeof child.content === 'string') {
|
|
82
|
-
child.content = stripLeadingPrefix(child.content, markerText)
|
|
83
|
-
break
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
182
|
const getLabelPrefixMarkerMatch = (inlineToken, markerReg) => {
|
|
89
183
|
if (!markerReg || !inlineToken || inlineToken.type !== 'inline') return null
|
|
90
184
|
const content = typeof inlineToken.content === 'string' ? inlineToken.content : ''
|
|
@@ -127,20 +221,6 @@ const normalizeAutoLabelNumberSets = (value) => {
|
|
|
127
221
|
return normalized
|
|
128
222
|
}
|
|
129
223
|
|
|
130
|
-
const buildLabelClassLookup = (opt) => {
|
|
131
|
-
const classPrefix = opt.classPrefix ? opt.classPrefix + '-' : ''
|
|
132
|
-
const defaultClasses = [classPrefix + 'label']
|
|
133
|
-
const withType = (type) => {
|
|
134
|
-
if (opt.removeMarkNameInCaptionClass) return defaultClasses
|
|
135
|
-
return [classPrefix + type + '-label', ...defaultClasses]
|
|
136
|
-
}
|
|
137
|
-
return {
|
|
138
|
-
img: withType('img'),
|
|
139
|
-
table: withType('table'),
|
|
140
|
-
default: defaultClasses,
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
224
|
const shouldApplyLabelNumbering = (captionType, opt) => {
|
|
145
225
|
const setting = opt.autoLabelNumberSets
|
|
146
226
|
if (!setting) return false
|
|
@@ -219,63 +299,23 @@ const getImageAltText = (token) => {
|
|
|
219
299
|
|
|
220
300
|
const getImageTitleText = (token) => getTokenAttr(token, 'title')
|
|
221
301
|
|
|
222
|
-
const
|
|
223
|
-
const target = (text || '').trim()
|
|
224
|
-
if (!target) return 'en'
|
|
225
|
-
for (let i = 0; i < target.length; i++) {
|
|
226
|
-
const char = target[i]
|
|
227
|
-
const code = target.charCodeAt(i)
|
|
228
|
-
if (isJapaneseCharCode(code)) return 'ja'
|
|
229
|
-
if (isSentenceBoundaryChar(char) || char === '\n') break
|
|
230
|
-
}
|
|
231
|
-
return 'en'
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const isJapaneseCharCode = (code) => {
|
|
235
|
-
return (
|
|
236
|
-
(code >= 0x3040 && code <= 0x30ff) || // Hiragana + Katakana
|
|
237
|
-
(code >= 0x31f0 && code <= 0x31ff) || // Katakana extensions
|
|
238
|
-
(code >= 0x4e00 && code <= 0x9fff) || // CJK Unified Ideographs
|
|
239
|
-
(code >= 0xff66 && code <= 0xff9f) // Half-width Katakana
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const isSentenceBoundaryChar = (char) => {
|
|
244
|
-
return char === '.' || char === '!' || char === '?' || char === '。' || char === '!' || char === '?'
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const getAutoFallbackLabel = (text, captionType) => {
|
|
248
|
-
const type = captionType === 'table' ? 'table' : 'img'
|
|
249
|
-
const lang = detectCaptionLanguage(text)
|
|
250
|
-
const defaults = fallbackLabelDefaults[type] || fallbackLabelDefaults.img
|
|
251
|
-
if (lang === 'ja') return defaults.ja || defaults.en || ''
|
|
252
|
-
return defaults.en || defaults.ja || ''
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const getPersistedFallbackLabel = (text, captionType, fallbackState) => {
|
|
256
|
-
const type = captionType === 'table' ? 'table' : 'img'
|
|
257
|
-
if (!fallbackState) return getAutoFallbackLabel(text, type)
|
|
258
|
-
if (fallbackState[type]) return fallbackState[type]
|
|
259
|
-
const resolved = getAutoFallbackLabel(text, type)
|
|
260
|
-
fallbackState[type] = resolved
|
|
261
|
-
return resolved
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const buildCaptionWithFallback = (text, fallbackOption, captionType = 'img', fallbackState) => {
|
|
302
|
+
const buildCaptionWithFallback = (text, fallbackOption, mark, markRegState, preferredLanguages) => {
|
|
265
303
|
const trimmedText = (text || '').trim()
|
|
266
304
|
if (!fallbackOption) return ''
|
|
305
|
+
if (!trimmedText) return ''
|
|
267
306
|
let label = ''
|
|
307
|
+
let generatedDefaults = null
|
|
268
308
|
if (typeof fallbackOption === 'string') {
|
|
269
309
|
label = fallbackOption.trim()
|
|
270
310
|
} else if (fallbackOption === true) {
|
|
271
|
-
|
|
311
|
+
generatedDefaults = getGeneratedLabelDefaults(mark, trimmedText, markRegState, preferredLanguages)
|
|
312
|
+
label = generatedDefaults && generatedDefaults.label ? generatedDefaults.label : ''
|
|
272
313
|
}
|
|
273
|
-
if (!label) return trimmedText
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
return isAsciiLabel ? label + '.' : label
|
|
314
|
+
if (!label) return fallbackOption === true ? '' : trimmedText
|
|
315
|
+
if (generatedDefaults) {
|
|
316
|
+
return label + (generatedDefaults.joint || '') + (generatedDefaults.space || '') + trimmedText
|
|
277
317
|
}
|
|
278
|
-
return label + (
|
|
318
|
+
return label + (asciiLabelReg.test(label) ? '. ' : ' ') + trimmedText
|
|
279
319
|
}
|
|
280
320
|
|
|
281
321
|
const createAutoCaptionParagraph = (captionText, TokenConstructor) => {
|
|
@@ -401,73 +441,71 @@ const ensureAutoFigureNumbering = (tokens, range, caption, figureNumberState, op
|
|
|
401
441
|
updateInlineTokenContent(inlineToken, originalText, newLabelText)
|
|
402
442
|
}
|
|
403
443
|
|
|
404
|
-
const matchAutoCaptionText = (text,
|
|
405
|
-
if (!text || !
|
|
444
|
+
const matchAutoCaptionText = (text, opt, preferredMark = 'img') => {
|
|
445
|
+
if (!text || !opt || !opt.markRegState) return ''
|
|
406
446
|
const trimmed = text.trim()
|
|
407
|
-
if (trimmed
|
|
447
|
+
if (!trimmed) return ''
|
|
448
|
+
const analysis = analyzeCaptionStart(trimmed, {
|
|
449
|
+
markRegState: opt.markRegState,
|
|
450
|
+
preferredMark,
|
|
451
|
+
})
|
|
452
|
+
if (analysis) return trimmed
|
|
408
453
|
return ''
|
|
409
454
|
}
|
|
410
455
|
|
|
411
|
-
const getAutoCaptionFromImage = (imageToken, opt
|
|
412
|
-
const imgCaptionMarkReg = opt && opt.imgCaptionMarkReg ? opt.imgCaptionMarkReg : null
|
|
456
|
+
const getAutoCaptionFromImage = (imageToken, opt) => {
|
|
413
457
|
if (!opt.autoCaptionDetection) return ''
|
|
414
|
-
if (!
|
|
458
|
+
if (!opt.autoAltCaption && !opt.autoTitleCaption && !(opt.markRegState && opt.markRegState.markReg && opt.markRegState.markReg.img)) return ''
|
|
415
459
|
|
|
416
460
|
const altText = getImageAltText(imageToken)
|
|
417
|
-
let caption = matchAutoCaptionText(altText,
|
|
461
|
+
let caption = matchAutoCaptionText(altText, opt)
|
|
418
462
|
if (caption) {
|
|
419
463
|
clearImageAltAttr(imageToken)
|
|
420
464
|
return caption
|
|
421
465
|
}
|
|
422
466
|
if (!caption && opt.autoAltCaption) {
|
|
423
467
|
const altForFallback = altText || ''
|
|
424
|
-
|
|
425
|
-
if (imageToken) {
|
|
468
|
+
const fallbackCaption = buildCaptionWithFallback(altForFallback, opt.autoAltCaption, 'img', opt.markRegState, opt.preferredLanguages)
|
|
469
|
+
if (fallbackCaption && imageToken) {
|
|
426
470
|
clearImageAltAttr(imageToken)
|
|
427
471
|
}
|
|
472
|
+
caption = fallbackCaption
|
|
428
473
|
}
|
|
429
474
|
if (caption) return caption
|
|
430
475
|
|
|
431
476
|
const titleText = getImageTitleText(imageToken)
|
|
432
|
-
caption = matchAutoCaptionText(titleText,
|
|
477
|
+
caption = matchAutoCaptionText(titleText, opt)
|
|
433
478
|
if (caption) {
|
|
434
479
|
clearImageTitleAttr(imageToken)
|
|
435
480
|
return caption
|
|
436
481
|
}
|
|
437
482
|
if (!caption && opt.autoTitleCaption) {
|
|
438
483
|
const titleForFallback = titleText || ''
|
|
439
|
-
|
|
440
|
-
if (imageToken) {
|
|
484
|
+
const fallbackCaption = buildCaptionWithFallback(titleForFallback, opt.autoTitleCaption, 'img', opt.markRegState, opt.preferredLanguages)
|
|
485
|
+
if (fallbackCaption && imageToken) {
|
|
441
486
|
clearImageTitleAttr(imageToken)
|
|
442
487
|
}
|
|
488
|
+
caption = fallbackCaption
|
|
443
489
|
}
|
|
444
490
|
return caption
|
|
445
491
|
}
|
|
446
492
|
|
|
447
|
-
const
|
|
448
|
-
if (htmlRegCache.has(tag)) return htmlRegCache.get(tag)
|
|
449
|
-
const regexStr = `^<${tag} ?[^>]*?>[\\s\\S]*?<\\/${tag}>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)`
|
|
450
|
-
const reg = new RegExp(regexStr)
|
|
451
|
-
htmlRegCache.set(tag, reg)
|
|
452
|
-
return reg
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const checkPrevCaption = (tokens, n, caption, fNum, sp, opt, captionState) => {
|
|
493
|
+
const checkPrevCaption = (tokens, n, caption, sp, opt, captionState) => {
|
|
456
494
|
if(n < 3) return caption
|
|
457
495
|
const captionStartToken = tokens[n-3]
|
|
458
496
|
const captionInlineToken = tokens[n-2]
|
|
459
497
|
const captionEndToken = tokens[n-1]
|
|
460
498
|
if (captionStartToken === undefined || captionEndToken === undefined) return
|
|
461
499
|
if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
|
|
462
|
-
setCaptionParagraph(n-3, captionState, caption,
|
|
500
|
+
setCaptionParagraph(n-3, captionState, caption, null, sp, opt)
|
|
463
501
|
const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
|
|
464
502
|
if(!captionName) {
|
|
465
503
|
if (opt.labelPrefixMarkerWithoutLabelPrevReg) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
504
|
+
const markerMatch = getLabelPrefixMarkerMatch(captionInlineToken, opt.labelPrefixMarkerWithoutLabelPrevReg)
|
|
505
|
+
if (markerMatch) {
|
|
506
|
+
stripLabelPrefixMarker(captionInlineToken, markerMatch)
|
|
507
|
+
caption.isPrev = true
|
|
508
|
+
}
|
|
471
509
|
}
|
|
472
510
|
return
|
|
473
511
|
}
|
|
@@ -476,22 +514,22 @@ const checkPrevCaption = (tokens, n, caption, fNum, sp, opt, captionState) => {
|
|
|
476
514
|
return
|
|
477
515
|
}
|
|
478
516
|
|
|
479
|
-
const checkNextCaption = (tokens, en, caption,
|
|
517
|
+
const checkNextCaption = (tokens, en, caption, sp, opt, captionState) => {
|
|
480
518
|
if (en + 2 > tokens.length) return
|
|
481
519
|
const captionStartToken = tokens[en+1]
|
|
482
520
|
const captionInlineToken = tokens[en+2]
|
|
483
521
|
const captionEndToken = tokens[en+3]
|
|
484
522
|
if (captionStartToken === undefined || captionEndToken === undefined) return
|
|
485
523
|
if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
|
|
486
|
-
setCaptionParagraph(en+1, captionState, caption,
|
|
524
|
+
setCaptionParagraph(en+1, captionState, caption, null, sp, opt)
|
|
487
525
|
const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
|
|
488
526
|
if(!captionName) {
|
|
489
527
|
if (opt.labelPrefixMarkerWithoutLabelNextReg) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
528
|
+
const markerMatch = getLabelPrefixMarkerMatch(captionInlineToken, opt.labelPrefixMarkerWithoutLabelNextReg)
|
|
529
|
+
if (markerMatch) {
|
|
530
|
+
stripLabelPrefixMarker(captionInlineToken, markerMatch)
|
|
531
|
+
caption.isNext = true
|
|
532
|
+
}
|
|
495
533
|
}
|
|
496
534
|
return
|
|
497
535
|
}
|
|
@@ -613,18 +651,18 @@ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInstea
|
|
|
613
651
|
breakToken.content = '\n'
|
|
614
652
|
return breakToken
|
|
615
653
|
}
|
|
616
|
-
if (
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (caption.name === 'img' && tokens[n].attrs) {
|
|
624
|
-
for (let i = 0; i < tokens[n].attrs.length; i++) {
|
|
625
|
-
const attr = tokens[n].attrs[i]
|
|
626
|
-
figureStartToken.attrJoin(attr[0], attr[1])
|
|
654
|
+
if (caption.name === 'img') {
|
|
655
|
+
const joinAttrs = (attrs) => {
|
|
656
|
+
if (!attrs || attrs.length === 0) return
|
|
657
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
658
|
+
const attr = attrs[i]
|
|
659
|
+
figureStartToken.attrJoin(attr[0], attr[1])
|
|
660
|
+
}
|
|
627
661
|
}
|
|
662
|
+
// `styleProcess` should keep working even when markdown-it-attrs is absent.
|
|
663
|
+
if (opt.styleProcess) joinAttrs(sp.attrs)
|
|
664
|
+
// Forward attrs already materialized by markdown-it-attrs on the image paragraph.
|
|
665
|
+
joinAttrs(tokens[n].attrs)
|
|
628
666
|
}
|
|
629
667
|
if (replaceInsteadOfWrap) {
|
|
630
668
|
tokens.splice(en, 1, createBreakToken(), figureEndToken, createBreakToken())
|
|
@@ -640,10 +678,10 @@ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInstea
|
|
|
640
678
|
return
|
|
641
679
|
}
|
|
642
680
|
|
|
643
|
-
const checkCaption = (tokens, n, en, caption,
|
|
644
|
-
checkPrevCaption(tokens, n, caption,
|
|
681
|
+
const checkCaption = (tokens, n, en, caption, sp, opt, captionState) => {
|
|
682
|
+
checkPrevCaption(tokens, n, caption, sp, opt, captionState)
|
|
645
683
|
if (caption.isPrev) return
|
|
646
|
-
checkNextCaption(tokens, en, caption,
|
|
684
|
+
checkNextCaption(tokens, en, caption, sp, opt, captionState)
|
|
647
685
|
return
|
|
648
686
|
}
|
|
649
687
|
|
|
@@ -736,129 +774,38 @@ const detectFenceToken = (token, n, caption) => {
|
|
|
736
774
|
}
|
|
737
775
|
}
|
|
738
776
|
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const hasBlockquoteHint = content.indexOf('<blockquote') !== -1
|
|
747
|
-
const hasDivHint = content.indexOf('<div') !== -1
|
|
748
|
-
const hasIframeTag = hasIframeHint || (hasDivHint && iframeTagReg.test(content))
|
|
749
|
-
const hasBlueskyEmbed = hasBlueskyHint && blueskyEmbedReg.test(content)
|
|
750
|
-
if (!hasBlueskyHint
|
|
751
|
-
&& !hasVideoHint
|
|
752
|
-
&& !hasAudioHint
|
|
753
|
-
&& !hasIframeHint
|
|
754
|
-
&& !hasBlockquoteHint
|
|
755
|
-
&& !hasDivHint) {
|
|
756
|
-
return null
|
|
757
|
-
}
|
|
758
|
-
let matchedTag = ''
|
|
759
|
-
for (let i = 0; i < HTML_TAG_CANDIDATES.length; i++) {
|
|
760
|
-
const candidate = HTML_TAG_CANDIDATES[i]
|
|
761
|
-
const treatDivAsIframe = candidate === 'div'
|
|
762
|
-
const lookupTag = treatDivAsIframe ? 'div' : candidate
|
|
763
|
-
let hasTagHint = false
|
|
764
|
-
if (candidate === 'video') {
|
|
765
|
-
hasTagHint = hasVideoHint
|
|
766
|
-
} else if (candidate === 'audio') {
|
|
767
|
-
hasTagHint = hasAudioHint
|
|
768
|
-
} else if (candidate === 'iframe') {
|
|
769
|
-
hasTagHint = hasIframeHint
|
|
770
|
-
} else if (candidate === 'blockquote') {
|
|
771
|
-
hasTagHint = hasBlockquoteHint
|
|
772
|
-
} else {
|
|
773
|
-
hasTagHint = hasDivHint
|
|
774
|
-
}
|
|
775
|
-
if (candidate === 'div' && !hasIframeTag) continue
|
|
776
|
-
if (!hasTagHint && !(candidate === 'blockquote' && hasBlueskyEmbed)) continue
|
|
777
|
-
const hasTag = hasTagHint ? content.match(getHtmlReg(lookupTag)) : null
|
|
778
|
-
const isBlueskyBlockquote = !hasTag && hasBlueskyEmbed && candidate === 'blockquote'
|
|
779
|
-
if (!(hasTag || isBlueskyBlockquote)) continue
|
|
780
|
-
if (hasTag) {
|
|
781
|
-
if ((hasTag[2] && hasTag[3] !== '\n') || (hasTag[1] !== '\n' && hasTag[2] === undefined)) {
|
|
782
|
-
token.content += '\n'
|
|
783
|
-
}
|
|
784
|
-
matchedTag = treatDivAsIframe ? 'iframe' : candidate
|
|
785
|
-
if (treatDivAsIframe) {
|
|
786
|
-
sp.isVideoIframe = true
|
|
787
|
-
}
|
|
788
|
-
} else {
|
|
789
|
-
let addedCont = ''
|
|
790
|
-
let j = n + 1
|
|
791
|
-
while (j < tokens.length) {
|
|
792
|
-
const nextToken = tokens[j]
|
|
793
|
-
if (nextToken.type === 'inline' && endBlockquoteScriptReg.test(nextToken.content)) {
|
|
794
|
-
addedCont += nextToken.content + '\n'
|
|
795
|
-
if (tokens[j + 1] && tokens[j + 1].type === 'paragraph_close') {
|
|
796
|
-
tokens.splice(j + 1, 1)
|
|
797
|
-
}
|
|
798
|
-
nextToken.content = ''
|
|
799
|
-
if (nextToken.children) {
|
|
800
|
-
for (let k = 0; k < nextToken.children.length; k++) {
|
|
801
|
-
nextToken.children[k].content = ''
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
break
|
|
805
|
-
}
|
|
806
|
-
if (nextToken.type === 'paragraph_open') {
|
|
807
|
-
addedCont += '\n'
|
|
808
|
-
tokens.splice(j, 1)
|
|
809
|
-
continue
|
|
810
|
-
}
|
|
811
|
-
j++
|
|
812
|
-
}
|
|
813
|
-
token.content += addedCont
|
|
814
|
-
matchedTag = 'blockquote'
|
|
815
|
-
}
|
|
816
|
-
break
|
|
817
|
-
}
|
|
818
|
-
if (!matchedTag) return null
|
|
819
|
-
if (matchedTag === 'blockquote') {
|
|
820
|
-
if (token.content.indexOf('class="') !== -1 && classNameReg.test(token.content)) {
|
|
821
|
-
sp.isIframeTypeBlockquote = true
|
|
822
|
-
} else {
|
|
823
|
-
return null
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
if (matchedTag === 'iframe' && videoIframeReg.test(token.content)) {
|
|
827
|
-
sp.isVideoIframe = true
|
|
828
|
-
}
|
|
829
|
-
caption.name = matchedTag
|
|
830
|
-
let wrapWithoutCaption = false
|
|
831
|
-
const htmlWrapWithoutCaption = opt.htmlWrapWithoutCaption
|
|
832
|
-
if (matchedTag === 'blockquote') {
|
|
833
|
-
wrapWithoutCaption = !!(sp.isIframeTypeBlockquote && htmlWrapWithoutCaption && htmlWrapWithoutCaption.iframeTypeBlockquote)
|
|
834
|
-
} else if (htmlWrapWithoutCaption) {
|
|
835
|
-
wrapWithoutCaption = !!htmlWrapWithoutCaption[matchedTag]
|
|
836
|
-
}
|
|
837
|
-
return {
|
|
838
|
-
type: 'html',
|
|
839
|
-
tagName: matchedTag,
|
|
840
|
-
en: n,
|
|
841
|
-
replaceInsteadOfWrap: false,
|
|
842
|
-
wrapWithoutCaption,
|
|
843
|
-
canWrap: true,
|
|
844
|
-
}
|
|
777
|
+
const hasLeadingImageChild = (token) => {
|
|
778
|
+
return !!(token &&
|
|
779
|
+
token.type === 'inline' &&
|
|
780
|
+
token.children &&
|
|
781
|
+
token.children.length > 0 &&
|
|
782
|
+
token.children[0] &&
|
|
783
|
+
token.children[0].type === 'image')
|
|
845
784
|
}
|
|
846
785
|
|
|
847
|
-
const detectImageParagraph = (
|
|
848
|
-
if (!token || token.type !== 'paragraph_open') return null
|
|
849
|
-
if (!nextToken || nextToken.type !== 'inline' || !nextToken.children || nextToken.children.length === 0) return null
|
|
850
|
-
if (nextToken.children[0].type !== 'image') return null
|
|
851
|
-
|
|
786
|
+
const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
852
787
|
const multipleImagesEnabled = !!opt.multipleImages
|
|
853
788
|
const styleProcessEnabled = !!opt.styleProcess
|
|
854
789
|
const allowSingleImageWithoutCaption = !!opt.oneImageWithoutCaption
|
|
790
|
+
const children = nextToken.children
|
|
791
|
+
const imageToken = children[0]
|
|
792
|
+
const childrenLength = children.length
|
|
855
793
|
let imageNum = 1
|
|
856
794
|
let isMultipleImagesHorizontal = true
|
|
857
795
|
let isMultipleImagesVertical = true
|
|
858
796
|
let isValid = true
|
|
859
797
|
caption.name = 'img'
|
|
860
|
-
|
|
861
|
-
|
|
798
|
+
if (childrenLength === 1) {
|
|
799
|
+
return {
|
|
800
|
+
type: 'image',
|
|
801
|
+
tagName: 'img',
|
|
802
|
+
en: n + 2,
|
|
803
|
+
replaceInsteadOfWrap: true,
|
|
804
|
+
wrapWithoutCaption: allowSingleImageWithoutCaption,
|
|
805
|
+
canWrap: true,
|
|
806
|
+
imageToken,
|
|
807
|
+
}
|
|
808
|
+
}
|
|
862
809
|
if (!multipleImagesEnabled && childrenLength > 2) {
|
|
863
810
|
return {
|
|
864
811
|
type: 'image',
|
|
@@ -867,7 +814,7 @@ const detectImageParagraph = (tokens, token, nextToken, n, caption, sp, opt) =>
|
|
|
867
814
|
replaceInsteadOfWrap: true,
|
|
868
815
|
wrapWithoutCaption: false,
|
|
869
816
|
canWrap: false,
|
|
870
|
-
imageToken
|
|
817
|
+
imageToken,
|
|
871
818
|
}
|
|
872
819
|
}
|
|
873
820
|
for (let childIndex = 1; childIndex < childrenLength; childIndex++) {
|
|
@@ -882,6 +829,7 @@ const detectImageParagraph = (tokens, token, nextToken, n, caption, sp, opt) =>
|
|
|
882
829
|
for (let i = 0; i < parsedAttrs.length; i++) {
|
|
883
830
|
sp.attrs.push(parsedAttrs[i])
|
|
884
831
|
}
|
|
832
|
+
child.content = ''
|
|
885
833
|
}
|
|
886
834
|
break
|
|
887
835
|
}
|
|
@@ -936,31 +884,29 @@ const detectImageParagraph = (tokens, token, nextToken, n, caption, sp, opt) =>
|
|
|
936
884
|
replaceInsteadOfWrap: true,
|
|
937
885
|
wrapWithoutCaption: isValid && allowSingleImageWithoutCaption,
|
|
938
886
|
canWrap: isValid,
|
|
939
|
-
imageToken
|
|
887
|
+
imageToken,
|
|
940
888
|
}
|
|
941
889
|
}
|
|
942
890
|
|
|
943
891
|
const figureWithCaption = (state, opt) => {
|
|
944
|
-
let fNum = {
|
|
945
|
-
img: 0,
|
|
946
|
-
table: 0,
|
|
947
|
-
}
|
|
948
|
-
|
|
949
892
|
const figureNumberState = {
|
|
950
893
|
img: 0,
|
|
951
894
|
table: 0,
|
|
952
895
|
}
|
|
953
896
|
|
|
954
|
-
const fallbackLabelState = {
|
|
955
|
-
img: null,
|
|
956
|
-
table: null,
|
|
957
|
-
}
|
|
958
|
-
|
|
959
897
|
const captionState = { tokens: state.tokens, Token: state.Token }
|
|
960
|
-
|
|
898
|
+
const shouldResolvePreferredLanguages = !!(
|
|
899
|
+
opt.shouldResolvePreferredLanguages &&
|
|
900
|
+
sourceMayNeedPreferredLanguages(state)
|
|
901
|
+
)
|
|
902
|
+
const renderOpt = shouldResolvePreferredLanguages ? Object.create(opt) : opt
|
|
903
|
+
if (shouldResolvePreferredLanguages) {
|
|
904
|
+
renderOpt.preferredLanguages = resolvePreferredLanguagesForState(state, opt)
|
|
905
|
+
}
|
|
906
|
+
figureWithCaptionCore(state.tokens, renderOpt, figureNumberState, state.Token, captionState, null, 0)
|
|
961
907
|
}
|
|
962
908
|
|
|
963
|
-
const figureWithCaptionCore = (tokens, opt,
|
|
909
|
+
const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor, captionState, parentType = null, startIndex = 0) => {
|
|
964
910
|
const rRange = { start: startIndex, end: startIndex }
|
|
965
911
|
const rCaption = {
|
|
966
912
|
name: '', nameSuffix: '', isPrev: false, isNext: false
|
|
@@ -978,7 +924,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
978
924
|
const containerType = getNestedContainerType(token)
|
|
979
925
|
|
|
980
926
|
if (containerType && containerType !== 'blockquote') {
|
|
981
|
-
const closeIndex = figureWithCaptionCore(tokens, opt,
|
|
927
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, n + 1)
|
|
982
928
|
n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
|
|
983
929
|
continue
|
|
984
930
|
}
|
|
@@ -992,16 +938,23 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
992
938
|
const tokenType = token.type
|
|
993
939
|
const blockType = CHECK_TYPE_TOKEN_MAP[tokenType]
|
|
994
940
|
if (tokenType === 'paragraph_open') {
|
|
995
|
-
resetRangeState(rRange, n)
|
|
996
|
-
resetCaptionState(rCaption)
|
|
997
|
-
resetSpecialState(rSp)
|
|
998
941
|
const nextToken = tokens[n + 1]
|
|
999
|
-
|
|
942
|
+
if (hasLeadingImageChild(nextToken)) {
|
|
943
|
+
resetRangeState(rRange, n)
|
|
944
|
+
resetCaptionState(rCaption)
|
|
945
|
+
resetSpecialState(rSp)
|
|
946
|
+
detection = detectImageParagraph(nextToken, n, rCaption, rSp, opt)
|
|
947
|
+
}
|
|
1000
948
|
} else if (tokenType === 'html_block') {
|
|
1001
949
|
resetRangeState(rRange, n)
|
|
1002
950
|
resetCaptionState(rCaption)
|
|
1003
951
|
resetSpecialState(rSp)
|
|
1004
|
-
detection =
|
|
952
|
+
detection = detectHtmlFigureCandidate(tokens, token, n, opt.htmlWrapWithoutCaption)
|
|
953
|
+
if (detection) {
|
|
954
|
+
rCaption.name = detection.tagName
|
|
955
|
+
rSp.isVideoIframe = !!detection.isVideoIframe
|
|
956
|
+
rSp.isIframeTypeBlockquote = !!detection.isIframeTypeBlockquote
|
|
957
|
+
}
|
|
1005
958
|
} else if (tokenType === 'fence') {
|
|
1006
959
|
resetRangeState(rRange, n)
|
|
1007
960
|
resetCaptionState(rCaption)
|
|
@@ -1016,7 +969,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1016
969
|
|
|
1017
970
|
if (!detection) {
|
|
1018
971
|
if (containerType === 'blockquote') {
|
|
1019
|
-
const closeIndex = figureWithCaptionCore(tokens, opt,
|
|
972
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, n + 1)
|
|
1020
973
|
n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
|
|
1021
974
|
} else {
|
|
1022
975
|
n++
|
|
@@ -1027,13 +980,13 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1027
980
|
rRange.end = detection.en
|
|
1028
981
|
|
|
1029
982
|
rSp.figureClassName = resolveFigureClassName(detection.tagName, rSp, opt)
|
|
1030
|
-
checkCaption(tokens, rRange.start, rRange.end, rCaption,
|
|
983
|
+
checkCaption(tokens, rRange.start, rRange.end, rCaption, rSp, opt, captionState)
|
|
1031
984
|
applyCaptionDrivenFigureClass(rCaption, rSp, opt)
|
|
1032
985
|
|
|
1033
986
|
let hasCaption = rCaption.isPrev || rCaption.isNext
|
|
1034
987
|
let pendingAutoCaption = ''
|
|
1035
988
|
if (!hasCaption && detection.type === 'image' && opt.autoCaptionDetection) {
|
|
1036
|
-
pendingAutoCaption = getAutoCaptionFromImage(detection.imageToken, opt
|
|
989
|
+
pendingAutoCaption = getAutoCaptionFromImage(detection.imageToken, opt)
|
|
1037
990
|
if (pendingAutoCaption) {
|
|
1038
991
|
hasCaption = true
|
|
1039
992
|
}
|
|
@@ -1042,7 +995,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1042
995
|
if (detection.canWrap === false) {
|
|
1043
996
|
let nextIndex = rRange.end + 1
|
|
1044
997
|
if (containerType === 'blockquote') {
|
|
1045
|
-
const closeIndex = figureWithCaptionCore(tokens, opt,
|
|
998
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, rRange.start + 1)
|
|
1046
999
|
nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : rRange.end) + 1)
|
|
1047
1000
|
}
|
|
1048
1001
|
n = nextIndex
|
|
@@ -1069,7 +1022,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1069
1022
|
rRange.start += insertedLength
|
|
1070
1023
|
rRange.end += insertedLength
|
|
1071
1024
|
n += insertedLength
|
|
1072
|
-
checkCaption(tokens, rRange.start, rRange.end, rCaption,
|
|
1025
|
+
checkCaption(tokens, rRange.start, rRange.end, rCaption, rSp, opt, captionState)
|
|
1073
1026
|
applyCaptionDrivenFigureClass(rCaption, rSp, opt)
|
|
1074
1027
|
}
|
|
1075
1028
|
ensureAutoFigureNumbering(tokens, rRange, rCaption, figureNumberState, opt)
|
|
@@ -1097,7 +1050,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1097
1050
|
}
|
|
1098
1051
|
|
|
1099
1052
|
if (containerType === 'blockquote') {
|
|
1100
|
-
const closeIndex = figureWithCaptionCore(tokens, opt,
|
|
1053
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, rRange.start + 1)
|
|
1101
1054
|
const fallbackIndex = rCaption.name ? rRange.end : n
|
|
1102
1055
|
nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : fallbackIndex) + 1)
|
|
1103
1056
|
}
|
|
@@ -1111,12 +1064,13 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1111
1064
|
let opt = {
|
|
1112
1065
|
// Caption languages delegated to p-captions.
|
|
1113
1066
|
languages: ['en', 'ja'],
|
|
1067
|
+
preferredLanguages: null, // optional tie-break order for generated fallback labels; defaults to inferred document order / languages
|
|
1114
1068
|
|
|
1115
1069
|
// --- figure-wrapper behavior ---
|
|
1116
1070
|
classPrefix: 'f',
|
|
1117
1071
|
figureClassThatWrapsIframeTypeBlockquote: null,
|
|
1118
1072
|
figureClassThatWrapsSlides: null,
|
|
1119
|
-
styleProcess
|
|
1073
|
+
styleProcess: true,
|
|
1120
1074
|
oneImageWithoutCaption: false,
|
|
1121
1075
|
iframeWithoutCaption: false,
|
|
1122
1076
|
videoWithoutCaption: false,
|
|
@@ -1163,11 +1117,13 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1163
1117
|
if (!hasExplicitLabelClassFollowsFigure && opt.figureToLabelClassMap) {
|
|
1164
1118
|
opt.labelClassFollowsFigure = true
|
|
1165
1119
|
}
|
|
1166
|
-
opt.
|
|
1120
|
+
opt.classPrefix = normalizeOptionalClassName(opt.classPrefix)
|
|
1121
|
+
opt.allIframeTypeFigureClassName = normalizeOptionalClassName(opt.allIframeTypeFigureClassName)
|
|
1167
1122
|
opt.markRegState = getMarkRegStateForLanguages(opt.languages)
|
|
1168
|
-
opt.
|
|
1169
|
-
|
|
1170
|
-
|
|
1123
|
+
opt.preferredLanguages = normalizePreferredLanguages(opt.preferredLanguages, opt.markRegState.languages)
|
|
1124
|
+
if (opt.preferredLanguages.length === 0) opt.preferredLanguages = null
|
|
1125
|
+
opt.normalizedOptionLanguages = normalizePreferredLanguages(opt.languages, opt.markRegState.languages)
|
|
1126
|
+
opt.shouldResolvePreferredLanguages = needsPreferredLanguagesResolution(opt)
|
|
1171
1127
|
opt.htmlWrapWithoutCaption = {
|
|
1172
1128
|
iframe: !!opt.iframeWithoutCaption,
|
|
1173
1129
|
video: !!opt.videoWithoutCaption,
|
|
@@ -1183,21 +1139,33 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1183
1139
|
const classPrefix = buildClassPrefix(opt.classPrefix)
|
|
1184
1140
|
opt.figureClassPrefix = classPrefix
|
|
1185
1141
|
opt.captionClassPrefix = classPrefix
|
|
1142
|
+
const defaultIframeTypeBlockquoteClass = classPrefix + 'img'
|
|
1143
|
+
const defaultSlideFigureClass = classPrefix + 'slide'
|
|
1186
1144
|
if (!hasExplicitFigureClassThatWrapsIframeTypeBlockquote) {
|
|
1187
|
-
opt.figureClassThatWrapsIframeTypeBlockquote =
|
|
1145
|
+
opt.figureClassThatWrapsIframeTypeBlockquote = defaultIframeTypeBlockquoteClass
|
|
1146
|
+
} else {
|
|
1147
|
+
opt.figureClassThatWrapsIframeTypeBlockquote = normalizeClassOptionWithFallback(
|
|
1148
|
+
opt.figureClassThatWrapsIframeTypeBlockquote,
|
|
1149
|
+
defaultIframeTypeBlockquoteClass,
|
|
1150
|
+
)
|
|
1188
1151
|
}
|
|
1189
1152
|
if (!hasExplicitFigureClassThatWrapsSlides) {
|
|
1190
|
-
opt.figureClassThatWrapsSlides =
|
|
1153
|
+
opt.figureClassThatWrapsSlides = defaultSlideFigureClass
|
|
1154
|
+
} else {
|
|
1155
|
+
opt.figureClassThatWrapsSlides = normalizeClassOptionWithFallback(
|
|
1156
|
+
opt.figureClassThatWrapsSlides,
|
|
1157
|
+
defaultSlideFigureClass,
|
|
1158
|
+
)
|
|
1191
1159
|
}
|
|
1192
1160
|
// Precompute label-class permutations so numbering lookup doesn't rebuild arrays per caption.
|
|
1193
1161
|
opt.labelClassLookup = buildLabelClassLookup(opt)
|
|
1194
1162
|
const markerList = normalizeLabelPrefixMarkers(opt.labelPrefixMarker)
|
|
1195
|
-
opt.labelPrefixMarkerReg =
|
|
1163
|
+
opt.labelPrefixMarkerReg = buildLabelPrefixMarkerRegFromMarkers(markerList)
|
|
1196
1164
|
opt.cleanCaptionRegCache = new Map()
|
|
1197
1165
|
if (opt.allowLabelPrefixMarkerWithoutLabel === true) {
|
|
1198
1166
|
const markerPair = resolveLabelPrefixMarkerPair(markerList)
|
|
1199
|
-
opt.labelPrefixMarkerWithoutLabelPrevReg =
|
|
1200
|
-
opt.labelPrefixMarkerWithoutLabelNextReg =
|
|
1167
|
+
opt.labelPrefixMarkerWithoutLabelPrevReg = buildLabelPrefixMarkerRegFromMarkers(markerPair.prev)
|
|
1168
|
+
opt.labelPrefixMarkerWithoutLabelNextReg = buildLabelPrefixMarkerRegFromMarkers(markerPair.next)
|
|
1201
1169
|
} else {
|
|
1202
1170
|
opt.labelPrefixMarkerWithoutLabelPrevReg = null
|
|
1203
1171
|
opt.labelPrefixMarkerWithoutLabelNextReg = null
|
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.17.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,19 +20,20 @@
|
|
|
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.
|
|
23
|
+
"@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.10",
|
|
24
|
+
"@peaceroad/markdown-it-renderer-fence": "^0.6.1",
|
|
25
|
+
"@peaceroad/markdown-it-renderer-image": "^0.13.0",
|
|
26
|
+
"@peaceroad/markdown-it-strong-ja": "^0.9.0",
|
|
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": "
|
|
32
|
+
"p7d-markdown-it-p-captions": "0.22.0"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"index.js",
|
|
36
|
+
"embeds/",
|
|
36
37
|
"README.md",
|
|
37
38
|
"LICENSE"
|
|
38
39
|
]
|