@peaceroad/markdown-it-figure-with-p-caption 0.18.0 → 0.19.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 +5 -1
- package/embeds/detect.js +11 -7
- package/embeds/providers.js +0 -1
- package/index.js +104 -112
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ Optionally, you can auto-number image and table caption paragraphs starting from
|
|
|
52
52
|
|
|
53
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
|
+
- `videoWithoutCaption` classifies an iframe (including one nested in a `<div>` wrapper) as video only when its `src` host is one of the known YouTube/Vimeo hosts. A generic iframe wrapper still requires `iframeWithoutCaption`.
|
|
55
56
|
- 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
57
|
- `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
58
|
- All other iframes fall back to `<figure class="f-iframe">` unless you override the class via `allIframeTypeFigureClassName`.
|
|
@@ -102,9 +103,10 @@ Every option below is forwarded verbatim to `p7d-markdown-it-p-captions`, which
|
|
|
102
103
|
|
|
103
104
|
- `autoLabelNumberSets`: enable numbering per media type. Pass an array such as `['img']`, `['table']`, or `['img', 'table']`.
|
|
104
105
|
- `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.
|
|
106
|
+
- Do not pass p-captions' lower-level `setFigureNumber` option to this plugin. Figure numbering is owned by `autoLabelNumber` / `autoLabelNumberSets`; `setFigureNumber` is rejected during setup to prevent two numbering systems from mutating the same caption.
|
|
105
107
|
- Counters start at `1` near the top of the document and increment sequentially per media type. Figures and tables keep independent counters even when mixed together.
|
|
106
108
|
- The counter only advances when a real caption exists (paragraph, auto-detected alt/title, or fallback text). Figures emitted solely because of `imageOnlyParagraphWithoutCaption` / `oneImageWithoutCaption` stay unnumbered.
|
|
107
|
-
- Manual numbers inside the caption text (e.g., `Figure 5.`) always win. The plugin updates its
|
|
109
|
+
- Manual numbers inside the caption text (e.g., `Figure 5.`) always win. The plugin reads the exact `captionDecision.number` supplied by `p7d-markdown-it-p-captions` and updates its decimal counter so the next automatic number becomes `6`. Explicit compound/alphanumeric numbers (e.g., `Figure A.` or `Figure A.5.`) are preserved without appending an automatic number and do not seed the decimal counter. This applies to captions sourced from paragraphs, auto detection, and fallback captions.
|
|
108
110
|
|
|
109
111
|
## Basic Usage
|
|
110
112
|
|
|
@@ -362,6 +364,8 @@ Slide. A Speaker Deck.
|
|
|
362
364
|
|
|
363
365
|
### Auto alt/title detection
|
|
364
366
|
|
|
367
|
+
The consumed `alt` or `title` value is cleared only after the image paragraph is confirmed to be wrappable. Tight-list images and image paragraphs with trailing non-image text keep their original accessibility attributes and baseline rendering.
|
|
368
|
+
|
|
365
369
|
```
|
|
366
370
|
[Markdown]
|
|
367
371
|

|
package/embeds/detect.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
|
|
7
7
|
const htmlRegCache = new Map()
|
|
8
8
|
const openingClassAttrReg = /^<[^>]*?\bclass=(?:"([^"]*)"|'([^']*)')/i
|
|
9
|
-
const
|
|
9
|
+
const iframeSrcAttrReg = /<iframe\b[^>]*?\bsrc=(?:"([^"]*)"|'([^']*)')/i
|
|
10
10
|
const endBlockquoteScriptReg = /<\/blockquote> *<script[^>]*?><\/script>$/i
|
|
11
11
|
const targetHtmlHintReg = /<(?:video|audio|iframe|blockquote|div)\b/i
|
|
12
12
|
const blueskyEmbedHintReg = /bluesky-embed/i
|
|
@@ -90,6 +90,13 @@ const getOpeningAttrValue = (content, reg) => {
|
|
|
90
90
|
return match[1] || match[2] || ''
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
const getAttrValue = (content, reg) => {
|
|
94
|
+
if (typeof content !== 'string') return ''
|
|
95
|
+
const match = content.match(reg)
|
|
96
|
+
if (!match) return ''
|
|
97
|
+
return match[1] || match[2] || ''
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
const hasKnownBlockquoteEmbedClass = (content) => {
|
|
94
101
|
const classAttr = getOpeningAttrValue(content, openingClassAttrReg)
|
|
95
102
|
if (!classAttr) return false
|
|
@@ -106,14 +113,14 @@ const hasKnownBlockquoteEmbedClass = (content) => {
|
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
const isKnownVideoIframe = (content) => {
|
|
109
|
-
const src =
|
|
116
|
+
const src = getAttrValue(content, iframeSrcAttrReg)
|
|
110
117
|
if (!src || src.slice(0, 8).toLowerCase() !== 'https://') return false
|
|
111
118
|
const slashIndex = src.indexOf('/', 8)
|
|
112
119
|
const host = (slashIndex === -1 ? src.slice(8) : src.slice(8, slashIndex)).toLowerCase()
|
|
113
120
|
return VIDEO_IFRAME_HOSTS.has(host)
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
const detectHtmlTagCandidate = (tokens, token, startIndex, detector, hints
|
|
123
|
+
const detectHtmlTagCandidate = (tokens, token, startIndex, detector, hints) => {
|
|
117
124
|
if (detector.requiresIframeTag && !hints.hasIframeTag) return ''
|
|
118
125
|
const hasTagHint = !!(detector.hintKey && hints[detector.hintKey])
|
|
119
126
|
const allowBlueskyFallback = detector.candidate === 'blockquote' && hints.hasBlueskyHint
|
|
@@ -123,9 +130,6 @@ const detectHtmlTagCandidate = (tokens, token, startIndex, detector, hints, resu
|
|
|
123
130
|
if (!hasTag && !isBlueskyFallback) return ''
|
|
124
131
|
if (hasTag) {
|
|
125
132
|
appendHtmlBlockNewlineIfNeeded(token, hasTag)
|
|
126
|
-
if (detector.treatAsVideoIframe) {
|
|
127
|
-
result.isVideoIframe = true
|
|
128
|
-
}
|
|
129
133
|
return detector.matchedTag || detector.candidate
|
|
130
134
|
}
|
|
131
135
|
consumeBlockquoteEmbedScript(tokens, token, startIndex)
|
|
@@ -155,7 +159,7 @@ export const detectHtmlFigureCandidate = (tokens, token, startIndex, htmlWrapWit
|
|
|
155
159
|
|
|
156
160
|
let matchedTag = ''
|
|
157
161
|
for (let i = 0; i < HTML_EMBED_CANDIDATES.length; i++) {
|
|
158
|
-
matchedTag = detectHtmlTagCandidate(tokens, token, startIndex, HTML_EMBED_CANDIDATES[i], hints
|
|
162
|
+
matchedTag = detectHtmlTagCandidate(tokens, token, startIndex, HTML_EMBED_CANDIDATES[i], hints)
|
|
159
163
|
if (matchedTag) break
|
|
160
164
|
}
|
|
161
165
|
if (!matchedTag) return null
|
package/embeds/providers.js
CHANGED
package/index.js
CHANGED
|
@@ -22,6 +22,9 @@ const CHECK_TYPE_TOKEN_MAP = {
|
|
|
22
22
|
pre_open: 'pre',
|
|
23
23
|
blockquote_open: 'blockquote',
|
|
24
24
|
}
|
|
25
|
+
const hasOwnOption = (option, name) => !!(
|
|
26
|
+
option && Object.prototype.hasOwnProperty.call(option, name)
|
|
27
|
+
)
|
|
25
28
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
26
29
|
const normalizeLanguageCode = (value) => {
|
|
27
30
|
if (value === null || value === undefined) return ''
|
|
@@ -190,6 +193,7 @@ const resolvePreferredLanguagesForState = (state, opt) => {
|
|
|
190
193
|
}
|
|
191
194
|
const needsPreferredLanguagesResolution = (opt) => {
|
|
192
195
|
if (!opt || !opt.markRegState || !Array.isArray(opt.markRegState.languages)) return false
|
|
196
|
+
if (!opt.autoCaptionDetection) return false
|
|
193
197
|
if (opt.markRegState.languages.length <= 1) return false
|
|
194
198
|
return opt.autoAltCaption === true || opt.autoTitleCaption === true
|
|
195
199
|
}
|
|
@@ -306,7 +310,7 @@ const normalizeAutoLabelNumberSets = (value) => {
|
|
|
306
310
|
if (!value) return normalized
|
|
307
311
|
if (Array.isArray(value)) {
|
|
308
312
|
for (const entry of value) {
|
|
309
|
-
if (
|
|
313
|
+
if (entry === 'img' || entry === 'table') normalized[entry] = true
|
|
310
314
|
}
|
|
311
315
|
return normalized
|
|
312
316
|
}
|
|
@@ -505,29 +509,18 @@ const updateInlineTokenContent = (inlineToken, originalText, newText) => {
|
|
|
505
509
|
inlineToken.content.slice(index + originalText.length)
|
|
506
510
|
}
|
|
507
511
|
|
|
508
|
-
const
|
|
512
|
+
const parsePositiveInteger = (text) => {
|
|
509
513
|
if (typeof text !== 'string' || text.length === 0) return null
|
|
510
|
-
let end = text.length - 1
|
|
511
|
-
while (end >= 0 && text.charCodeAt(end) === 0x20) end--
|
|
512
|
-
if (end < 0) return null
|
|
513
|
-
const lastCode = text.charCodeAt(end)
|
|
514
|
-
if (lastCode < 0x30 || lastCode > 0x39) return null
|
|
515
|
-
let start = end
|
|
516
|
-
while (start >= 0) {
|
|
517
|
-
const code = text.charCodeAt(start)
|
|
518
|
-
if (code < 0x30 || code > 0x39) break
|
|
519
|
-
start--
|
|
520
|
-
}
|
|
521
514
|
let value = 0
|
|
522
|
-
let
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
515
|
+
for (let i = 0; i < text.length; i++) {
|
|
516
|
+
const code = text.charCodeAt(i)
|
|
517
|
+
if (code < 0x30 || code > 0x39) return null
|
|
518
|
+
value = value * 10 + code - 0x30
|
|
526
519
|
}
|
|
527
520
|
return value
|
|
528
521
|
}
|
|
529
522
|
|
|
530
|
-
const ensureAutoFigureNumbering = (tokens, range, caption, figureNumberState, opt) => {
|
|
523
|
+
const ensureAutoFigureNumbering = (tokens, range, caption, figureNumberState, sp, opt) => {
|
|
531
524
|
const captionType = caption.name === 'img' ? 'img' : (caption.name === 'table' ? 'table' : '')
|
|
532
525
|
if (!captionType) return
|
|
533
526
|
if (!shouldApplyLabelNumbering(captionType, opt)) return
|
|
@@ -536,19 +529,12 @@ const ensureAutoFigureNumbering = (tokens, range, caption, figureNumberState, op
|
|
|
536
529
|
const labelTextToken = getInlineLabelTextToken(inlineToken, captionType, opt)
|
|
537
530
|
if (!labelTextToken || typeof labelTextToken.content !== 'string') return
|
|
538
531
|
const originalText = labelTextToken.content
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (code >= 0x30 && code <= 0x39) {
|
|
544
|
-
const explicitValue = parseTrailingPositiveInteger(originalText)
|
|
545
|
-
if (explicitValue !== null) {
|
|
546
|
-
if (explicitValue > (figureNumberState[captionType] || 0)) {
|
|
547
|
-
figureNumberState[captionType] = explicitValue
|
|
548
|
-
}
|
|
549
|
-
return
|
|
550
|
-
}
|
|
532
|
+
if (sp && sp.captionDecision && sp.captionDecision.hasExplicitNumber) {
|
|
533
|
+
const explicitValue = parsePositiveInteger(sp.captionDecision.number)
|
|
534
|
+
if (explicitValue !== null && explicitValue > (figureNumberState[captionType] || 0)) {
|
|
535
|
+
figureNumberState[captionType] = explicitValue
|
|
551
536
|
}
|
|
537
|
+
return
|
|
552
538
|
}
|
|
553
539
|
figureNumberState[captionType] = (figureNumberState[captionType] || 0) + 1
|
|
554
540
|
const baseLabel = originalText.trim()
|
|
@@ -571,89 +557,87 @@ const matchAutoCaptionText = (text, opt, preferredMark = 'img') => {
|
|
|
571
557
|
return ''
|
|
572
558
|
}
|
|
573
559
|
|
|
574
|
-
const getAutoCaptionFromImage = (imageToken, opt) => {
|
|
575
|
-
if (!opt.
|
|
576
|
-
if (!opt.autoAltCaption && !opt.autoTitleCaption && !(opt.markRegState && opt.markRegState.markReg && opt.markRegState.markReg.img)) return ''
|
|
560
|
+
const getAutoCaptionFromImage = (imageToken, opt, preferredLanguages) => {
|
|
561
|
+
if (!opt.autoAltCaption && !opt.autoTitleCaption && !(opt.markRegState && opt.markRegState.markReg && opt.markRegState.markReg.img)) return null
|
|
577
562
|
|
|
578
563
|
const altText = getImageAltText(imageToken)
|
|
579
564
|
let caption = matchAutoCaptionText(altText, opt)
|
|
580
565
|
if (caption) {
|
|
581
|
-
|
|
582
|
-
return caption
|
|
566
|
+
return { text: caption, source: 'alt' }
|
|
583
567
|
}
|
|
584
|
-
if (
|
|
568
|
+
if (opt.autoAltCaption) {
|
|
585
569
|
const altForFallback = altText || ''
|
|
586
|
-
const fallbackCaption = buildCaptionWithFallback(altForFallback, opt.autoAltCaption, 'img', opt.markRegState,
|
|
587
|
-
if (fallbackCaption && imageToken) {
|
|
588
|
-
clearImageAltAttr(imageToken)
|
|
589
|
-
}
|
|
570
|
+
const fallbackCaption = buildCaptionWithFallback(altForFallback, opt.autoAltCaption, 'img', opt.markRegState, preferredLanguages)
|
|
590
571
|
caption = fallbackCaption
|
|
591
572
|
}
|
|
592
|
-
if (caption) return caption
|
|
573
|
+
if (caption) return { text: caption, source: 'alt' }
|
|
593
574
|
|
|
594
575
|
const titleText = getImageTitleText(imageToken)
|
|
595
576
|
caption = matchAutoCaptionText(titleText, opt)
|
|
596
577
|
if (caption) {
|
|
597
|
-
|
|
598
|
-
return caption
|
|
578
|
+
return { text: caption, source: 'title' }
|
|
599
579
|
}
|
|
600
|
-
if (
|
|
580
|
+
if (opt.autoTitleCaption) {
|
|
601
581
|
const titleForFallback = titleText || ''
|
|
602
|
-
const fallbackCaption = buildCaptionWithFallback(titleForFallback, opt.autoTitleCaption, 'img', opt.markRegState,
|
|
603
|
-
if (fallbackCaption && imageToken) {
|
|
604
|
-
clearImageTitleAttr(imageToken)
|
|
605
|
-
}
|
|
582
|
+
const fallbackCaption = buildCaptionWithFallback(titleForFallback, opt.autoTitleCaption, 'img', opt.markRegState, preferredLanguages)
|
|
606
583
|
caption = fallbackCaption
|
|
607
584
|
}
|
|
608
|
-
return caption
|
|
585
|
+
return caption ? { text: caption, source: 'title' } : null
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const consumeAutoCaptionSource = (imageToken, autoCaption) => {
|
|
589
|
+
if (!imageToken || !autoCaption) return
|
|
590
|
+
if (autoCaption.source === 'alt') {
|
|
591
|
+
clearImageAltAttr(imageToken)
|
|
592
|
+
} else if (autoCaption.source === 'title') {
|
|
593
|
+
clearImageTitleAttr(imageToken)
|
|
594
|
+
}
|
|
609
595
|
}
|
|
610
596
|
|
|
611
597
|
const checkPrevCaption = (tokens, n, caption, sp, opt, captionState) => {
|
|
612
|
-
if(n < 3) return
|
|
613
|
-
const captionStartToken = tokens[n-3]
|
|
614
|
-
const captionInlineToken = tokens[n-2]
|
|
615
|
-
const captionEndToken = tokens[n-1]
|
|
598
|
+
if (n < 3) return
|
|
599
|
+
const captionStartToken = tokens[n - 3]
|
|
600
|
+
const captionInlineToken = tokens[n - 2]
|
|
601
|
+
const captionEndToken = tokens[n - 1]
|
|
616
602
|
if (captionStartToken === undefined || captionEndToken === undefined) return
|
|
617
603
|
if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
|
|
618
|
-
setCaptionParagraph(n-3, captionState, caption, null, sp, opt)
|
|
604
|
+
setCaptionParagraph(n - 3, captionState, caption, null, sp, opt)
|
|
619
605
|
const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
|
|
620
|
-
if(!captionName) {
|
|
606
|
+
if (!captionName) {
|
|
621
607
|
if (opt.labelPrefixMarkerWithoutLabelPrevReg) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
608
|
+
const markerMatch = getLabelPrefixMarkerMatch(captionInlineToken, opt.labelPrefixMarkerWithoutLabelPrevReg)
|
|
609
|
+
if (markerMatch) {
|
|
610
|
+
stripLabelPrefixMarker(captionInlineToken, markerMatch)
|
|
611
|
+
caption.isPrev = true
|
|
612
|
+
}
|
|
627
613
|
}
|
|
628
614
|
return
|
|
629
615
|
}
|
|
630
616
|
caption.name = captionName
|
|
631
617
|
caption.isPrev = true
|
|
632
|
-
return
|
|
633
618
|
}
|
|
634
619
|
|
|
635
620
|
const checkNextCaption = (tokens, en, caption, sp, opt, captionState) => {
|
|
636
|
-
if (en +
|
|
637
|
-
const captionStartToken = tokens[en+1]
|
|
638
|
-
const captionInlineToken = tokens[en+2]
|
|
639
|
-
const captionEndToken = tokens[en+3]
|
|
621
|
+
if (en + 3 >= tokens.length) return
|
|
622
|
+
const captionStartToken = tokens[en + 1]
|
|
623
|
+
const captionInlineToken = tokens[en + 2]
|
|
624
|
+
const captionEndToken = tokens[en + 3]
|
|
640
625
|
if (captionStartToken === undefined || captionEndToken === undefined) return
|
|
641
626
|
if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
|
|
642
|
-
setCaptionParagraph(en+1, captionState, caption, null, sp, opt)
|
|
627
|
+
setCaptionParagraph(en + 1, captionState, caption, null, sp, opt)
|
|
643
628
|
const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
|
|
644
|
-
if(!captionName) {
|
|
629
|
+
if (!captionName) {
|
|
645
630
|
if (opt.labelPrefixMarkerWithoutLabelNextReg) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
631
|
+
const markerMatch = getLabelPrefixMarkerMatch(captionInlineToken, opt.labelPrefixMarkerWithoutLabelNextReg)
|
|
632
|
+
if (markerMatch) {
|
|
633
|
+
stripLabelPrefixMarker(captionInlineToken, markerMatch)
|
|
634
|
+
caption.isNext = true
|
|
635
|
+
}
|
|
651
636
|
}
|
|
652
637
|
return
|
|
653
638
|
}
|
|
654
639
|
caption.name = captionName
|
|
655
640
|
caption.isNext = true
|
|
656
|
-
return
|
|
657
641
|
}
|
|
658
642
|
|
|
659
643
|
const cleanCaptionTokenAttrs = (token, captionName, opt) => {
|
|
@@ -730,7 +714,6 @@ const changePrevCaptionPosition = (tokens, n, caption, opt) => {
|
|
|
730
714
|
captionEndToken.level = figureBaseLevel + 1
|
|
731
715
|
tokens.splice(n + 2, 0, captionStartToken, captionInlineToken, captionEndToken)
|
|
732
716
|
tokens.splice(n-3, 3)
|
|
733
|
-
return true
|
|
734
717
|
}
|
|
735
718
|
|
|
736
719
|
const changeNextCaptionPosition = (tokens, en, caption, opt) => {
|
|
@@ -750,7 +733,6 @@ const changeNextCaptionPosition = (tokens, en, caption, opt) => {
|
|
|
750
733
|
captionEndToken.level = figureBaseLevel + 1
|
|
751
734
|
tokens.splice(en, 0, captionStartToken, captionInlineToken, captionEndToken)
|
|
752
735
|
tokens.splice(en+4, 3)
|
|
753
|
-
return true
|
|
754
736
|
}
|
|
755
737
|
|
|
756
738
|
const getTokenMap = (token) => {
|
|
@@ -852,14 +834,12 @@ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInstea
|
|
|
852
834
|
}
|
|
853
835
|
range.start = n
|
|
854
836
|
range.end = en
|
|
855
|
-
return
|
|
856
837
|
}
|
|
857
838
|
|
|
858
839
|
const checkCaption = (tokens, n, en, caption, sp, opt, captionState) => {
|
|
859
840
|
checkPrevCaption(tokens, n, caption, sp, opt, captionState)
|
|
860
841
|
if (caption.isPrev) return
|
|
861
842
|
checkNextCaption(tokens, en, caption, sp, opt, captionState)
|
|
862
|
-
return
|
|
863
843
|
}
|
|
864
844
|
|
|
865
845
|
const getNestedContainerType = (token) => {
|
|
@@ -971,6 +951,7 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
971
951
|
let isMultipleImagesHorizontal = true
|
|
972
952
|
let isMultipleImagesVertical = true
|
|
973
953
|
let isValid = true
|
|
954
|
+
let trailingAttrToken = null
|
|
974
955
|
caption.name = 'img'
|
|
975
956
|
if (childrenLength === 1) {
|
|
976
957
|
return {
|
|
@@ -1006,7 +987,7 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
1006
987
|
for (let i = 0; i < parsedAttrs.length; i++) {
|
|
1007
988
|
sp.attrs.push(parsedAttrs[i])
|
|
1008
989
|
}
|
|
1009
|
-
|
|
990
|
+
trailingAttrToken = child
|
|
1010
991
|
break
|
|
1011
992
|
}
|
|
1012
993
|
}
|
|
@@ -1044,12 +1025,6 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
1044
1025
|
} else {
|
|
1045
1026
|
caption.nameSuffix = '-multiple'
|
|
1046
1027
|
}
|
|
1047
|
-
for (let i = 0; i < childrenLength; i++) {
|
|
1048
|
-
const child = children[i]
|
|
1049
|
-
if (isOnlySpacesText(child)) {
|
|
1050
|
-
child.content = ''
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
1028
|
}
|
|
1054
1029
|
const en = n + 2
|
|
1055
1030
|
let tagName = 'img'
|
|
@@ -1062,6 +1037,22 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
1062
1037
|
wrapWithoutCaption: isValid && allowImageParagraphWithoutCaption,
|
|
1063
1038
|
canWrap: isValid,
|
|
1064
1039
|
imageToken,
|
|
1040
|
+
inlineToken: nextToken,
|
|
1041
|
+
trailingAttrToken,
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const applyImageParagraphTransform = (detection) => {
|
|
1046
|
+
if (!detection || detection.type !== 'image') return
|
|
1047
|
+
if (detection.trailingAttrToken) {
|
|
1048
|
+
detection.trailingAttrToken.content = ''
|
|
1049
|
+
}
|
|
1050
|
+
if (!detection.inlineToken || detection.tagName === 'img') return
|
|
1051
|
+
const children = detection.inlineToken.children
|
|
1052
|
+
for (let i = 0; i < children.length; i++) {
|
|
1053
|
+
if (isOnlySpacesText(children[i])) {
|
|
1054
|
+
children[i].content = ''
|
|
1055
|
+
}
|
|
1065
1056
|
}
|
|
1066
1057
|
}
|
|
1067
1058
|
|
|
@@ -1071,16 +1062,19 @@ const figureWithCaption = (state, opt) => {
|
|
|
1071
1062
|
table: 0,
|
|
1072
1063
|
}
|
|
1073
1064
|
|
|
1074
|
-
const captionState = {
|
|
1065
|
+
const captionState = {
|
|
1066
|
+
tokens: state.tokens,
|
|
1067
|
+
Token: state.Token,
|
|
1068
|
+
preferredLanguages: opt.preferredLanguages,
|
|
1069
|
+
}
|
|
1075
1070
|
const shouldResolvePreferredLanguages = !!(
|
|
1076
1071
|
opt.shouldResolvePreferredLanguages &&
|
|
1077
1072
|
sourceMayNeedPreferredLanguages(state)
|
|
1078
1073
|
)
|
|
1079
|
-
const renderOpt = shouldResolvePreferredLanguages ? Object.create(opt) : opt
|
|
1080
1074
|
if (shouldResolvePreferredLanguages) {
|
|
1081
|
-
|
|
1075
|
+
captionState.preferredLanguages = resolvePreferredLanguagesForState(state, opt)
|
|
1082
1076
|
}
|
|
1083
|
-
figureWithCaptionCore(state.tokens,
|
|
1077
|
+
figureWithCaptionCore(state.tokens, opt, figureNumberState, state.Token, captionState, null, 0)
|
|
1084
1078
|
}
|
|
1085
1079
|
|
|
1086
1080
|
const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor, captionState, parentType = null, startIndex = 0) => {
|
|
@@ -1161,9 +1155,9 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
|
|
|
1161
1155
|
applyCaptionDrivenFigureClass(rCaption, rSp, opt)
|
|
1162
1156
|
|
|
1163
1157
|
let hasCaption = rCaption.isPrev || rCaption.isNext
|
|
1164
|
-
let pendingAutoCaption =
|
|
1158
|
+
let pendingAutoCaption = null
|
|
1165
1159
|
if (!hasCaption && detection.type === 'image' && opt.autoCaptionDetection) {
|
|
1166
|
-
pendingAutoCaption = getAutoCaptionFromImage(detection.imageToken, opt)
|
|
1160
|
+
pendingAutoCaption = getAutoCaptionFromImage(detection.imageToken, opt, captionState.preferredLanguages)
|
|
1167
1161
|
if (pendingAutoCaption) {
|
|
1168
1162
|
hasCaption = true
|
|
1169
1163
|
}
|
|
@@ -1179,21 +1173,18 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
|
|
|
1179
1173
|
continue
|
|
1180
1174
|
}
|
|
1181
1175
|
|
|
1182
|
-
let shouldWrap =
|
|
1183
|
-
if (detection.type === 'html') {
|
|
1184
|
-
shouldWrap =
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
shouldWrap = false
|
|
1189
|
-
}
|
|
1190
|
-
} else {
|
|
1191
|
-
shouldWrap = detection.canWrap !== false && hasCaption
|
|
1176
|
+
let shouldWrap = hasCaption
|
|
1177
|
+
if (detection.type === 'html' || detection.type === 'image') {
|
|
1178
|
+
shouldWrap = shouldWrap || detection.wrapWithoutCaption
|
|
1179
|
+
}
|
|
1180
|
+
if (detection.type === 'image' && token.hidden === true) {
|
|
1181
|
+
shouldWrap = false
|
|
1192
1182
|
}
|
|
1193
1183
|
|
|
1194
1184
|
if (shouldWrap) {
|
|
1195
1185
|
if (pendingAutoCaption) {
|
|
1196
|
-
|
|
1186
|
+
consumeAutoCaptionSource(detection.imageToken, pendingAutoCaption)
|
|
1187
|
+
const captionTokens = createAutoCaptionParagraph(pendingAutoCaption.text, TokenConstructor)
|
|
1197
1188
|
tokens.splice(rRange.start, 0, ...captionTokens)
|
|
1198
1189
|
const insertedLength = captionTokens.length
|
|
1199
1190
|
rRange.start += insertedLength
|
|
@@ -1202,7 +1193,8 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
|
|
|
1202
1193
|
checkCaption(tokens, rRange.start, rRange.end, rCaption, rSp, opt, captionState)
|
|
1203
1194
|
applyCaptionDrivenFigureClass(rCaption, rSp, opt)
|
|
1204
1195
|
}
|
|
1205
|
-
|
|
1196
|
+
applyImageParagraphTransform(detection)
|
|
1197
|
+
ensureAutoFigureNumbering(tokens, rRange, rCaption, figureNumberState, rSp, opt)
|
|
1206
1198
|
wrapWithFigure(tokens, rRange, detection.tagName, rCaption, detection.replaceInsteadOfWrap, rSp, opt, TokenConstructor)
|
|
1207
1199
|
}
|
|
1208
1200
|
|
|
@@ -1221,8 +1213,6 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
|
|
|
1221
1213
|
} else if (rCaption.isNext) {
|
|
1222
1214
|
changeNextCaptionPosition(tokens, en, rCaption, opt)
|
|
1223
1215
|
nextIndex = en + 4
|
|
1224
|
-
} else {
|
|
1225
|
-
nextIndex = en + 1
|
|
1226
1216
|
}
|
|
1227
1217
|
}
|
|
1228
1218
|
|
|
@@ -1238,7 +1228,7 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
|
|
|
1238
1228
|
}
|
|
1239
1229
|
|
|
1240
1230
|
const mditFigureWithPCaption = (md, option) => {
|
|
1241
|
-
|
|
1231
|
+
const opt = {
|
|
1242
1232
|
// Caption languages delegated to p-captions.
|
|
1243
1233
|
languages: ['en', 'ja'],
|
|
1244
1234
|
preferredLanguages: null, // compatibility tie-break for generated fallback labels; prefer env.locale / env.preferredLocales per render
|
|
@@ -1287,16 +1277,18 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1287
1277
|
labelClassFollowsFigure: false,
|
|
1288
1278
|
figureToLabelClassMap: null,
|
|
1289
1279
|
}
|
|
1290
|
-
const hasExplicitAutoLabelNumberSets =
|
|
1291
|
-
const hasExplicitImageOnlyParagraphWithoutCaption =
|
|
1292
|
-
const hasExplicitFigureClassThatWrapsIframeTypeBlockquote =
|
|
1293
|
-
const hasExplicitFigureClassThatWrapsSlides =
|
|
1294
|
-
const hasExplicitLabelClassFollowsFigure =
|
|
1280
|
+
const hasExplicitAutoLabelNumberSets = hasOwnOption(option, 'autoLabelNumberSets')
|
|
1281
|
+
const hasExplicitImageOnlyParagraphWithoutCaption = hasOwnOption(option, 'imageOnlyParagraphWithoutCaption')
|
|
1282
|
+
const hasExplicitFigureClassThatWrapsIframeTypeBlockquote = hasOwnOption(option, 'figureClassThatWrapsIframeTypeBlockquote')
|
|
1283
|
+
const hasExplicitFigureClassThatWrapsSlides = hasOwnOption(option, 'figureClassThatWrapsSlides')
|
|
1284
|
+
const hasExplicitLabelClassFollowsFigure = hasOwnOption(option, 'labelClassFollowsFigure')
|
|
1285
|
+
if (hasOwnOption(option, 'setFigureNumber')) {
|
|
1286
|
+
throw new Error('setFigureNumber is not supported by figure-with-p-caption; use autoLabelNumber or autoLabelNumberSets')
|
|
1287
|
+
}
|
|
1295
1288
|
if (option) Object.assign(opt, option)
|
|
1296
1289
|
opt.imageOnlyParagraphWithoutCaption = hasExplicitImageOnlyParagraphWithoutCaption
|
|
1297
1290
|
? !!opt.imageOnlyParagraphWithoutCaption
|
|
1298
1291
|
: !!opt.oneImageWithoutCaption
|
|
1299
|
-
opt.oneImageWithoutCaption = !!opt.oneImageWithoutCaption
|
|
1300
1292
|
if (!hasExplicitLabelClassFollowsFigure && opt.figureToLabelClassMap) {
|
|
1301
1293
|
opt.labelClassFollowsFigure = true
|
|
1302
1294
|
}
|
|
@@ -1356,7 +1348,7 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1356
1348
|
opt.labelPrefixMarkerWithoutLabelNextReg = null
|
|
1357
1349
|
}
|
|
1358
1350
|
|
|
1359
|
-
//
|
|
1351
|
+
// Run after markdown-it-attrs has attached paragraph attrs, but before text replacements.
|
|
1360
1352
|
md.core.ruler.before('replacements', 'figure_with_caption', (state) => {
|
|
1361
1353
|
figureWithCaption(state, opt)
|
|
1362
1354
|
})
|
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.19.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.14",
|
|
24
|
+
"@peaceroad/markdown-it-renderer-fence": "^0.8.0",
|
|
25
|
+
"@peaceroad/markdown-it-renderer-image": "^0.16.0",
|
|
26
|
+
"@peaceroad/markdown-it-strong-ja": "^0.9.4",
|
|
27
27
|
"highlight.js": "^11.11.1",
|
|
28
|
-
"markdown-it": "^14.
|
|
29
|
-
"markdown-it-attrs": "^
|
|
28
|
+
"markdown-it": "^14.3.0",
|
|
29
|
+
"markdown-it-attrs": "^5.0.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"p7d-markdown-it-p-captions": "0.
|
|
32
|
+
"p7d-markdown-it-p-captions": "0.24.0"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"index.js",
|