@peaceroad/markdown-it-figure-with-p-caption 0.16.0 → 0.16.1
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 -4
- package/index.js +220 -152
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -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
|
|
|
@@ -437,13 +438,13 @@ A paragraph.
|
|
|
437
438
|
|
|
438
439
|
### Styles
|
|
439
440
|
|
|
440
|
-
This example uses `classPrefix: 'custom'` and leaves `styleProcess: true` so a trailing `{.notice}` block moves onto the `<figure>` wrapper.
|
|
441
|
+
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
442
|
|
|
442
443
|
```
|
|
443
444
|
[Markdown]
|
|
444
|
-
Figure. Highlighted cat.
|
|
445
|
+
Figure. Highlighted cat.
|
|
445
446
|
|
|
446
|
-

|
|
447
|
+
 {.notice}
|
|
447
448
|
[HTML]
|
|
448
449
|
<figure class="custom-img notice">
|
|
449
450
|
<figcaption><span class="custom-img-label">Figure<span class="custom-img-label-joint">.</span></span> Highlighted cat.</figcaption>
|
package/index.js
CHANGED
|
@@ -20,14 +20,36 @@ const CHECK_TYPE_TOKEN_MAP = {
|
|
|
20
20
|
pre_open: 'pre',
|
|
21
21
|
blockquote_open: 'blockquote',
|
|
22
22
|
}
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
23
|
+
const HTML_TAG_DETECTORS = [
|
|
24
|
+
{ candidate: 'video', lookupTag: 'video', hintKey: 'hasVideoHint' },
|
|
25
|
+
{ candidate: 'audio', lookupTag: 'audio', hintKey: 'hasAudioHint' },
|
|
26
|
+
{ candidate: 'iframe', lookupTag: 'iframe', hintKey: 'hasIframeHint' },
|
|
27
|
+
{ candidate: 'blockquote', lookupTag: 'blockquote', hintKey: 'hasBlockquoteHint' },
|
|
28
|
+
{
|
|
29
|
+
candidate: 'div',
|
|
30
|
+
lookupTag: 'div',
|
|
31
|
+
hintKey: 'hasDivHint',
|
|
32
|
+
requiresIframeTag: true,
|
|
33
|
+
matchedTag: 'iframe',
|
|
34
|
+
setVideoIframe: true,
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
const fallbackLabelDefaults = { en: 'Figure', ja: '図' }
|
|
28
38
|
|
|
29
39
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
30
|
-
const
|
|
40
|
+
const normalizeOptionalClassName = (value) => {
|
|
41
|
+
if (value === null || value === undefined) return ''
|
|
42
|
+
const normalized = String(value).trim()
|
|
43
|
+
return normalized || ''
|
|
44
|
+
}
|
|
45
|
+
const buildClassPrefix = (value) => {
|
|
46
|
+
const normalized = normalizeOptionalClassName(value)
|
|
47
|
+
return normalized ? normalized + '-' : ''
|
|
48
|
+
}
|
|
49
|
+
const normalizeClassOptionWithFallback = (value, fallbackValue) => {
|
|
50
|
+
const normalized = normalizeOptionalClassName(value)
|
|
51
|
+
return normalized || fallbackValue
|
|
52
|
+
}
|
|
31
53
|
const normalizeLanguages = (value) => {
|
|
32
54
|
if (!Array.isArray(value)) return ['en', 'ja']
|
|
33
55
|
const normalized = []
|
|
@@ -244,31 +266,28 @@ const isSentenceBoundaryChar = (char) => {
|
|
|
244
266
|
return char === '.' || char === '!' || char === '?' || char === '。' || char === '!' || char === '?'
|
|
245
267
|
}
|
|
246
268
|
|
|
247
|
-
const getAutoFallbackLabel = (text
|
|
248
|
-
const type = captionType === 'table' ? 'table' : 'img'
|
|
269
|
+
const getAutoFallbackLabel = (text) => {
|
|
249
270
|
const lang = detectCaptionLanguage(text)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return defaults.en || defaults.ja || ''
|
|
271
|
+
if (lang === 'ja') return fallbackLabelDefaults.ja || fallbackLabelDefaults.en || ''
|
|
272
|
+
return fallbackLabelDefaults.en || fallbackLabelDefaults.ja || ''
|
|
253
273
|
}
|
|
254
274
|
|
|
255
|
-
const getPersistedFallbackLabel = (text,
|
|
256
|
-
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
fallbackState[type] = resolved
|
|
275
|
+
const getPersistedFallbackLabel = (text, fallbackState) => {
|
|
276
|
+
if (!fallbackState) return getAutoFallbackLabel(text)
|
|
277
|
+
if (fallbackState.img) return fallbackState.img
|
|
278
|
+
const resolved = getAutoFallbackLabel(text)
|
|
279
|
+
fallbackState.img = resolved
|
|
261
280
|
return resolved
|
|
262
281
|
}
|
|
263
282
|
|
|
264
|
-
const buildCaptionWithFallback = (text, fallbackOption,
|
|
283
|
+
const buildCaptionWithFallback = (text, fallbackOption, fallbackState) => {
|
|
265
284
|
const trimmedText = (text || '').trim()
|
|
266
285
|
if (!fallbackOption) return ''
|
|
267
286
|
let label = ''
|
|
268
287
|
if (typeof fallbackOption === 'string') {
|
|
269
288
|
label = fallbackOption.trim()
|
|
270
289
|
} else if (fallbackOption === true) {
|
|
271
|
-
label = getPersistedFallbackLabel(trimmedText,
|
|
290
|
+
label = getPersistedFallbackLabel(trimmedText, fallbackState)
|
|
272
291
|
}
|
|
273
292
|
if (!label) return trimmedText
|
|
274
293
|
const isAsciiLabel = asciiLabelReg.test(label)
|
|
@@ -421,7 +440,7 @@ const getAutoCaptionFromImage = (imageToken, opt, fallbackLabelState) => {
|
|
|
421
440
|
}
|
|
422
441
|
if (!caption && opt.autoAltCaption) {
|
|
423
442
|
const altForFallback = altText || ''
|
|
424
|
-
caption = buildCaptionWithFallback(altForFallback, opt.autoAltCaption,
|
|
443
|
+
caption = buildCaptionWithFallback(altForFallback, opt.autoAltCaption, fallbackLabelState)
|
|
425
444
|
if (imageToken) {
|
|
426
445
|
clearImageAltAttr(imageToken)
|
|
427
446
|
}
|
|
@@ -436,7 +455,7 @@ const getAutoCaptionFromImage = (imageToken, opt, fallbackLabelState) => {
|
|
|
436
455
|
}
|
|
437
456
|
if (!caption && opt.autoTitleCaption) {
|
|
438
457
|
const titleForFallback = titleText || ''
|
|
439
|
-
caption = buildCaptionWithFallback(titleForFallback, opt.autoTitleCaption,
|
|
458
|
+
caption = buildCaptionWithFallback(titleForFallback, opt.autoTitleCaption, fallbackLabelState)
|
|
440
459
|
if (imageToken) {
|
|
441
460
|
clearImageTitleAttr(imageToken)
|
|
442
461
|
}
|
|
@@ -445,21 +464,118 @@ const getAutoCaptionFromImage = (imageToken, opt, fallbackLabelState) => {
|
|
|
445
464
|
}
|
|
446
465
|
|
|
447
466
|
const getHtmlReg = (tag) => {
|
|
448
|
-
|
|
467
|
+
const cached = htmlRegCache.get(tag)
|
|
468
|
+
if (cached) return cached
|
|
449
469
|
const regexStr = `^<${tag} ?[^>]*?>[\\s\\S]*?<\\/${tag}>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)`
|
|
450
470
|
const reg = new RegExp(regexStr)
|
|
451
471
|
htmlRegCache.set(tag, reg)
|
|
452
472
|
return reg
|
|
453
473
|
}
|
|
454
474
|
|
|
455
|
-
const
|
|
475
|
+
const getHtmlDetectionHints = (content) => {
|
|
476
|
+
const hasBlueskyHint = content.indexOf('bluesky-embed') !== -1
|
|
477
|
+
const hasVideoHint = content.indexOf('<video') !== -1
|
|
478
|
+
const hasAudioHint = content.indexOf('<audio') !== -1
|
|
479
|
+
const hasIframeHint = content.indexOf('<iframe') !== -1
|
|
480
|
+
const hasBlockquoteHint = content.indexOf('<blockquote') !== -1
|
|
481
|
+
const hasDivHint = content.indexOf('<div') !== -1
|
|
482
|
+
return {
|
|
483
|
+
hasBlueskyHint,
|
|
484
|
+
hasVideoHint,
|
|
485
|
+
hasAudioHint,
|
|
486
|
+
hasIframeHint,
|
|
487
|
+
hasBlockquoteHint,
|
|
488
|
+
hasDivHint,
|
|
489
|
+
hasIframeTag: hasIframeHint || (hasDivHint && iframeTagReg.test(content)),
|
|
490
|
+
hasBlueskyEmbed: hasBlueskyHint && blueskyEmbedReg.test(content),
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const hasAnyHtmlDetectionHint = (hints) => {
|
|
495
|
+
return !!(
|
|
496
|
+
hints.hasBlueskyHint ||
|
|
497
|
+
hints.hasVideoHint ||
|
|
498
|
+
hints.hasAudioHint ||
|
|
499
|
+
hints.hasIframeHint ||
|
|
500
|
+
hints.hasBlockquoteHint ||
|
|
501
|
+
hints.hasDivHint
|
|
502
|
+
)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const appendHtmlBlockNewlineIfNeeded = (token, hasTag) => {
|
|
506
|
+
if ((hasTag[2] && hasTag[3] !== '\n') || (hasTag[1] !== '\n' && hasTag[2] === undefined)) {
|
|
507
|
+
token.content += '\n'
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const consumeBlockquoteEmbedScript = (tokens, token, startIndex) => {
|
|
512
|
+
let addedCont = ''
|
|
513
|
+
let j = startIndex + 1
|
|
514
|
+
while (j < tokens.length) {
|
|
515
|
+
const nextToken = tokens[j]
|
|
516
|
+
if (nextToken.type === 'inline' && endBlockquoteScriptReg.test(nextToken.content)) {
|
|
517
|
+
addedCont += nextToken.content + '\n'
|
|
518
|
+
if (tokens[j + 1] && tokens[j + 1].type === 'paragraph_close') {
|
|
519
|
+
tokens.splice(j + 1, 1)
|
|
520
|
+
}
|
|
521
|
+
nextToken.content = ''
|
|
522
|
+
if (nextToken.children) {
|
|
523
|
+
for (let k = 0; k < nextToken.children.length; k++) {
|
|
524
|
+
nextToken.children[k].content = ''
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
break
|
|
528
|
+
}
|
|
529
|
+
if (nextToken.type === 'paragraph_open') {
|
|
530
|
+
addedCont += '\n'
|
|
531
|
+
tokens.splice(j, 1)
|
|
532
|
+
continue
|
|
533
|
+
}
|
|
534
|
+
j++
|
|
535
|
+
}
|
|
536
|
+
token.content += addedCont
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const detectHtmlTagCandidate = (tokens, token, startIndex, detector, hints, sp) => {
|
|
540
|
+
if (detector.requiresIframeTag && !hints.hasIframeTag) return ''
|
|
541
|
+
const hasTagHint = !!(detector.hintKey && hints[detector.hintKey])
|
|
542
|
+
const allowBlueskyFallback = detector.candidate === 'blockquote' && hints.hasBlueskyEmbed
|
|
543
|
+
if (!hasTagHint && !allowBlueskyFallback) return ''
|
|
544
|
+
const hasTag = hasTagHint ? token.content.match(getHtmlReg(detector.lookupTag)) : null
|
|
545
|
+
const isBlueskyBlockquote = detector.candidate === 'blockquote' && !hasTag && hints.hasBlueskyEmbed
|
|
546
|
+
if (!hasTag && !isBlueskyBlockquote) return ''
|
|
547
|
+
if (hasTag) {
|
|
548
|
+
appendHtmlBlockNewlineIfNeeded(token, hasTag)
|
|
549
|
+
if (detector.setVideoIframe) {
|
|
550
|
+
sp.isVideoIframe = true
|
|
551
|
+
}
|
|
552
|
+
return detector.matchedTag || detector.candidate
|
|
553
|
+
}
|
|
554
|
+
consumeBlockquoteEmbedScript(tokens, token, startIndex)
|
|
555
|
+
return 'blockquote'
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const isIframeTypeEmbedBlockquote = (content) => {
|
|
559
|
+
return content.indexOf('class="') !== -1 && classNameReg.test(content)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const resolveHtmlWrapWithoutCaption = (matchedTag, sp, opt) => {
|
|
563
|
+
const htmlWrapWithoutCaption = opt.htmlWrapWithoutCaption
|
|
564
|
+
if (!htmlWrapWithoutCaption) return false
|
|
565
|
+
if (matchedTag === 'blockquote') {
|
|
566
|
+
return !!(sp.isIframeTypeBlockquote && htmlWrapWithoutCaption.iframeTypeBlockquote)
|
|
567
|
+
}
|
|
568
|
+
return !!htmlWrapWithoutCaption[matchedTag]
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const checkPrevCaption = (tokens, n, caption, sp, opt, captionState) => {
|
|
456
572
|
if(n < 3) return caption
|
|
457
573
|
const captionStartToken = tokens[n-3]
|
|
458
574
|
const captionInlineToken = tokens[n-2]
|
|
459
575
|
const captionEndToken = tokens[n-1]
|
|
460
576
|
if (captionStartToken === undefined || captionEndToken === undefined) return
|
|
461
577
|
if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
|
|
462
|
-
setCaptionParagraph(n-3, captionState, caption,
|
|
578
|
+
setCaptionParagraph(n-3, captionState, caption, null, sp, opt)
|
|
463
579
|
const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
|
|
464
580
|
if(!captionName) {
|
|
465
581
|
if (opt.labelPrefixMarkerWithoutLabelPrevReg) {
|
|
@@ -476,14 +592,14 @@ const checkPrevCaption = (tokens, n, caption, fNum, sp, opt, captionState) => {
|
|
|
476
592
|
return
|
|
477
593
|
}
|
|
478
594
|
|
|
479
|
-
const checkNextCaption = (tokens, en, caption,
|
|
595
|
+
const checkNextCaption = (tokens, en, caption, sp, opt, captionState) => {
|
|
480
596
|
if (en + 2 > tokens.length) return
|
|
481
597
|
const captionStartToken = tokens[en+1]
|
|
482
598
|
const captionInlineToken = tokens[en+2]
|
|
483
599
|
const captionEndToken = tokens[en+3]
|
|
484
600
|
if (captionStartToken === undefined || captionEndToken === undefined) return
|
|
485
601
|
if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
|
|
486
|
-
setCaptionParagraph(en+1, captionState, caption,
|
|
602
|
+
setCaptionParagraph(en+1, captionState, caption, null, sp, opt)
|
|
487
603
|
const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
|
|
488
604
|
if(!captionName) {
|
|
489
605
|
if (opt.labelPrefixMarkerWithoutLabelNextReg) {
|
|
@@ -613,18 +729,18 @@ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInstea
|
|
|
613
729
|
breakToken.content = '\n'
|
|
614
730
|
return breakToken
|
|
615
731
|
}
|
|
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])
|
|
732
|
+
if (caption.name === 'img') {
|
|
733
|
+
const joinAttrs = (attrs) => {
|
|
734
|
+
if (!attrs || attrs.length === 0) return
|
|
735
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
736
|
+
const attr = attrs[i]
|
|
737
|
+
figureStartToken.attrJoin(attr[0], attr[1])
|
|
738
|
+
}
|
|
627
739
|
}
|
|
740
|
+
// `styleProcess` should keep working even when markdown-it-attrs is absent.
|
|
741
|
+
if (opt.styleProcess) joinAttrs(sp.attrs)
|
|
742
|
+
// Forward attrs already materialized by markdown-it-attrs on the image paragraph.
|
|
743
|
+
joinAttrs(tokens[n].attrs)
|
|
628
744
|
}
|
|
629
745
|
if (replaceInsteadOfWrap) {
|
|
630
746
|
tokens.splice(en, 1, createBreakToken(), figureEndToken, createBreakToken())
|
|
@@ -640,10 +756,10 @@ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInstea
|
|
|
640
756
|
return
|
|
641
757
|
}
|
|
642
758
|
|
|
643
|
-
const checkCaption = (tokens, n, en, caption,
|
|
644
|
-
checkPrevCaption(tokens, n, caption,
|
|
759
|
+
const checkCaption = (tokens, n, en, caption, sp, opt, captionState) => {
|
|
760
|
+
checkPrevCaption(tokens, n, caption, sp, opt, captionState)
|
|
645
761
|
if (caption.isPrev) return
|
|
646
|
-
checkNextCaption(tokens, en, caption,
|
|
762
|
+
checkNextCaption(tokens, en, caption, sp, opt, captionState)
|
|
647
763
|
return
|
|
648
764
|
}
|
|
649
765
|
|
|
@@ -738,86 +854,16 @@ const detectFenceToken = (token, n, caption) => {
|
|
|
738
854
|
|
|
739
855
|
const detectHtmlBlockToken = (tokens, token, n, caption, sp, opt) => {
|
|
740
856
|
if (!token || token.type !== 'html_block') return null
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
const hasVideoHint = content.indexOf('<video') !== -1
|
|
744
|
-
const hasAudioHint = content.indexOf('<audio') !== -1
|
|
745
|
-
const hasIframeHint = content.indexOf('<iframe') !== -1
|
|
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
|
-
}
|
|
857
|
+
const hints = getHtmlDetectionHints(token.content)
|
|
858
|
+
if (!hasAnyHtmlDetectionHint(hints)) return null
|
|
758
859
|
let matchedTag = ''
|
|
759
|
-
for (let i = 0; i <
|
|
760
|
-
|
|
761
|
-
|
|
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
|
|
860
|
+
for (let i = 0; i < HTML_TAG_DETECTORS.length; i++) {
|
|
861
|
+
matchedTag = detectHtmlTagCandidate(tokens, token, n, HTML_TAG_DETECTORS[i], hints, sp)
|
|
862
|
+
if (matchedTag) break
|
|
817
863
|
}
|
|
818
864
|
if (!matchedTag) return null
|
|
819
865
|
if (matchedTag === 'blockquote') {
|
|
820
|
-
if (
|
|
866
|
+
if (isIframeTypeEmbedBlockquote(token.content)) {
|
|
821
867
|
sp.isIframeTypeBlockquote = true
|
|
822
868
|
} else {
|
|
823
869
|
return null
|
|
@@ -827,13 +873,7 @@ const detectHtmlBlockToken = (tokens, token, n, caption, sp, opt) => {
|
|
|
827
873
|
sp.isVideoIframe = true
|
|
828
874
|
}
|
|
829
875
|
caption.name = matchedTag
|
|
830
|
-
|
|
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
|
-
}
|
|
876
|
+
const wrapWithoutCaption = resolveHtmlWrapWithoutCaption(matchedTag, sp, opt)
|
|
837
877
|
return {
|
|
838
878
|
type: 'html',
|
|
839
879
|
tagName: matchedTag,
|
|
@@ -844,21 +884,38 @@ const detectHtmlBlockToken = (tokens, token, n, caption, sp, opt) => {
|
|
|
844
884
|
}
|
|
845
885
|
}
|
|
846
886
|
|
|
847
|
-
const
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
887
|
+
const hasLeadingImageChild = (token) => {
|
|
888
|
+
return !!(token &&
|
|
889
|
+
token.type === 'inline' &&
|
|
890
|
+
token.children &&
|
|
891
|
+
token.children.length > 0 &&
|
|
892
|
+
token.children[0] &&
|
|
893
|
+
token.children[0].type === 'image')
|
|
894
|
+
}
|
|
851
895
|
|
|
896
|
+
const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
852
897
|
const multipleImagesEnabled = !!opt.multipleImages
|
|
853
898
|
const styleProcessEnabled = !!opt.styleProcess
|
|
854
899
|
const allowSingleImageWithoutCaption = !!opt.oneImageWithoutCaption
|
|
900
|
+
const children = nextToken.children
|
|
901
|
+
const imageToken = children[0]
|
|
902
|
+
const childrenLength = children.length
|
|
855
903
|
let imageNum = 1
|
|
856
904
|
let isMultipleImagesHorizontal = true
|
|
857
905
|
let isMultipleImagesVertical = true
|
|
858
906
|
let isValid = true
|
|
859
907
|
caption.name = 'img'
|
|
860
|
-
|
|
861
|
-
|
|
908
|
+
if (childrenLength === 1) {
|
|
909
|
+
return {
|
|
910
|
+
type: 'image',
|
|
911
|
+
tagName: 'img',
|
|
912
|
+
en: n + 2,
|
|
913
|
+
replaceInsteadOfWrap: true,
|
|
914
|
+
wrapWithoutCaption: allowSingleImageWithoutCaption,
|
|
915
|
+
canWrap: true,
|
|
916
|
+
imageToken,
|
|
917
|
+
}
|
|
918
|
+
}
|
|
862
919
|
if (!multipleImagesEnabled && childrenLength > 2) {
|
|
863
920
|
return {
|
|
864
921
|
type: 'image',
|
|
@@ -867,7 +924,7 @@ const detectImageParagraph = (tokens, token, nextToken, n, caption, sp, opt) =>
|
|
|
867
924
|
replaceInsteadOfWrap: true,
|
|
868
925
|
wrapWithoutCaption: false,
|
|
869
926
|
canWrap: false,
|
|
870
|
-
imageToken
|
|
927
|
+
imageToken,
|
|
871
928
|
}
|
|
872
929
|
}
|
|
873
930
|
for (let childIndex = 1; childIndex < childrenLength; childIndex++) {
|
|
@@ -882,6 +939,7 @@ const detectImageParagraph = (tokens, token, nextToken, n, caption, sp, opt) =>
|
|
|
882
939
|
for (let i = 0; i < parsedAttrs.length; i++) {
|
|
883
940
|
sp.attrs.push(parsedAttrs[i])
|
|
884
941
|
}
|
|
942
|
+
child.content = ''
|
|
885
943
|
}
|
|
886
944
|
break
|
|
887
945
|
}
|
|
@@ -936,16 +994,11 @@ const detectImageParagraph = (tokens, token, nextToken, n, caption, sp, opt) =>
|
|
|
936
994
|
replaceInsteadOfWrap: true,
|
|
937
995
|
wrapWithoutCaption: isValid && allowSingleImageWithoutCaption,
|
|
938
996
|
canWrap: isValid,
|
|
939
|
-
imageToken
|
|
997
|
+
imageToken,
|
|
940
998
|
}
|
|
941
999
|
}
|
|
942
1000
|
|
|
943
1001
|
const figureWithCaption = (state, opt) => {
|
|
944
|
-
let fNum = {
|
|
945
|
-
img: 0,
|
|
946
|
-
table: 0,
|
|
947
|
-
}
|
|
948
|
-
|
|
949
1002
|
const figureNumberState = {
|
|
950
1003
|
img: 0,
|
|
951
1004
|
table: 0,
|
|
@@ -953,14 +1006,13 @@ const figureWithCaption = (state, opt) => {
|
|
|
953
1006
|
|
|
954
1007
|
const fallbackLabelState = {
|
|
955
1008
|
img: null,
|
|
956
|
-
table: null,
|
|
957
1009
|
}
|
|
958
1010
|
|
|
959
1011
|
const captionState = { tokens: state.tokens, Token: state.Token }
|
|
960
|
-
figureWithCaptionCore(state.tokens, opt,
|
|
1012
|
+
figureWithCaptionCore(state.tokens, opt, figureNumberState, fallbackLabelState, state.Token, captionState, null, 0)
|
|
961
1013
|
}
|
|
962
1014
|
|
|
963
|
-
const figureWithCaptionCore = (tokens, opt,
|
|
1015
|
+
const figureWithCaptionCore = (tokens, opt, figureNumberState, fallbackLabelState, TokenConstructor, captionState, parentType = null, startIndex = 0) => {
|
|
964
1016
|
const rRange = { start: startIndex, end: startIndex }
|
|
965
1017
|
const rCaption = {
|
|
966
1018
|
name: '', nameSuffix: '', isPrev: false, isNext: false
|
|
@@ -978,7 +1030,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
978
1030
|
const containerType = getNestedContainerType(token)
|
|
979
1031
|
|
|
980
1032
|
if (containerType && containerType !== 'blockquote') {
|
|
981
|
-
const closeIndex = figureWithCaptionCore(tokens, opt,
|
|
1033
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, fallbackLabelState, TokenConstructor, captionState, containerType, n + 1)
|
|
982
1034
|
n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
|
|
983
1035
|
continue
|
|
984
1036
|
}
|
|
@@ -992,11 +1044,13 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
992
1044
|
const tokenType = token.type
|
|
993
1045
|
const blockType = CHECK_TYPE_TOKEN_MAP[tokenType]
|
|
994
1046
|
if (tokenType === 'paragraph_open') {
|
|
995
|
-
resetRangeState(rRange, n)
|
|
996
|
-
resetCaptionState(rCaption)
|
|
997
|
-
resetSpecialState(rSp)
|
|
998
1047
|
const nextToken = tokens[n + 1]
|
|
999
|
-
|
|
1048
|
+
if (hasLeadingImageChild(nextToken)) {
|
|
1049
|
+
resetRangeState(rRange, n)
|
|
1050
|
+
resetCaptionState(rCaption)
|
|
1051
|
+
resetSpecialState(rSp)
|
|
1052
|
+
detection = detectImageParagraph(nextToken, n, rCaption, rSp, opt)
|
|
1053
|
+
}
|
|
1000
1054
|
} else if (tokenType === 'html_block') {
|
|
1001
1055
|
resetRangeState(rRange, n)
|
|
1002
1056
|
resetCaptionState(rCaption)
|
|
@@ -1016,7 +1070,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1016
1070
|
|
|
1017
1071
|
if (!detection) {
|
|
1018
1072
|
if (containerType === 'blockquote') {
|
|
1019
|
-
const closeIndex = figureWithCaptionCore(tokens, opt,
|
|
1073
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, fallbackLabelState, TokenConstructor, captionState, containerType, n + 1)
|
|
1020
1074
|
n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
|
|
1021
1075
|
} else {
|
|
1022
1076
|
n++
|
|
@@ -1027,7 +1081,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1027
1081
|
rRange.end = detection.en
|
|
1028
1082
|
|
|
1029
1083
|
rSp.figureClassName = resolveFigureClassName(detection.tagName, rSp, opt)
|
|
1030
|
-
checkCaption(tokens, rRange.start, rRange.end, rCaption,
|
|
1084
|
+
checkCaption(tokens, rRange.start, rRange.end, rCaption, rSp, opt, captionState)
|
|
1031
1085
|
applyCaptionDrivenFigureClass(rCaption, rSp, opt)
|
|
1032
1086
|
|
|
1033
1087
|
let hasCaption = rCaption.isPrev || rCaption.isNext
|
|
@@ -1042,7 +1096,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1042
1096
|
if (detection.canWrap === false) {
|
|
1043
1097
|
let nextIndex = rRange.end + 1
|
|
1044
1098
|
if (containerType === 'blockquote') {
|
|
1045
|
-
const closeIndex = figureWithCaptionCore(tokens, opt,
|
|
1099
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, fallbackLabelState, TokenConstructor, captionState, containerType, rRange.start + 1)
|
|
1046
1100
|
nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : rRange.end) + 1)
|
|
1047
1101
|
}
|
|
1048
1102
|
n = nextIndex
|
|
@@ -1069,7 +1123,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1069
1123
|
rRange.start += insertedLength
|
|
1070
1124
|
rRange.end += insertedLength
|
|
1071
1125
|
n += insertedLength
|
|
1072
|
-
checkCaption(tokens, rRange.start, rRange.end, rCaption,
|
|
1126
|
+
checkCaption(tokens, rRange.start, rRange.end, rCaption, rSp, opt, captionState)
|
|
1073
1127
|
applyCaptionDrivenFigureClass(rCaption, rSp, opt)
|
|
1074
1128
|
}
|
|
1075
1129
|
ensureAutoFigureNumbering(tokens, rRange, rCaption, figureNumberState, opt)
|
|
@@ -1097,7 +1151,7 @@ const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLab
|
|
|
1097
1151
|
}
|
|
1098
1152
|
|
|
1099
1153
|
if (containerType === 'blockquote') {
|
|
1100
|
-
const closeIndex = figureWithCaptionCore(tokens, opt,
|
|
1154
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, fallbackLabelState, TokenConstructor, captionState, containerType, rRange.start + 1)
|
|
1101
1155
|
const fallbackIndex = rCaption.name ? rRange.end : n
|
|
1102
1156
|
nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : fallbackIndex) + 1)
|
|
1103
1157
|
}
|
|
@@ -1116,7 +1170,7 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1116
1170
|
classPrefix: 'f',
|
|
1117
1171
|
figureClassThatWrapsIframeTypeBlockquote: null,
|
|
1118
1172
|
figureClassThatWrapsSlides: null,
|
|
1119
|
-
styleProcess
|
|
1173
|
+
styleProcess: true,
|
|
1120
1174
|
oneImageWithoutCaption: false,
|
|
1121
1175
|
iframeWithoutCaption: false,
|
|
1122
1176
|
videoWithoutCaption: false,
|
|
@@ -1163,6 +1217,8 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1163
1217
|
if (!hasExplicitLabelClassFollowsFigure && opt.figureToLabelClassMap) {
|
|
1164
1218
|
opt.labelClassFollowsFigure = true
|
|
1165
1219
|
}
|
|
1220
|
+
opt.classPrefix = normalizeOptionalClassName(opt.classPrefix)
|
|
1221
|
+
opt.allIframeTypeFigureClassName = normalizeOptionalClassName(opt.allIframeTypeFigureClassName)
|
|
1166
1222
|
opt.languages = normalizeLanguages(opt.languages)
|
|
1167
1223
|
opt.markRegState = getMarkRegStateForLanguages(opt.languages)
|
|
1168
1224
|
opt.imgCaptionMarkReg = opt.markRegState && opt.markRegState.markReg
|
|
@@ -1183,11 +1239,23 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1183
1239
|
const classPrefix = buildClassPrefix(opt.classPrefix)
|
|
1184
1240
|
opt.figureClassPrefix = classPrefix
|
|
1185
1241
|
opt.captionClassPrefix = classPrefix
|
|
1242
|
+
const defaultIframeTypeBlockquoteClass = classPrefix + 'img'
|
|
1243
|
+
const defaultSlideFigureClass = classPrefix + 'slide'
|
|
1186
1244
|
if (!hasExplicitFigureClassThatWrapsIframeTypeBlockquote) {
|
|
1187
|
-
opt.figureClassThatWrapsIframeTypeBlockquote =
|
|
1245
|
+
opt.figureClassThatWrapsIframeTypeBlockquote = defaultIframeTypeBlockquoteClass
|
|
1246
|
+
} else {
|
|
1247
|
+
opt.figureClassThatWrapsIframeTypeBlockquote = normalizeClassOptionWithFallback(
|
|
1248
|
+
opt.figureClassThatWrapsIframeTypeBlockquote,
|
|
1249
|
+
defaultIframeTypeBlockquoteClass,
|
|
1250
|
+
)
|
|
1188
1251
|
}
|
|
1189
1252
|
if (!hasExplicitFigureClassThatWrapsSlides) {
|
|
1190
|
-
opt.figureClassThatWrapsSlides =
|
|
1253
|
+
opt.figureClassThatWrapsSlides = defaultSlideFigureClass
|
|
1254
|
+
} else {
|
|
1255
|
+
opt.figureClassThatWrapsSlides = normalizeClassOptionWithFallback(
|
|
1256
|
+
opt.figureClassThatWrapsSlides,
|
|
1257
|
+
defaultSlideFigureClass,
|
|
1258
|
+
)
|
|
1191
1259
|
}
|
|
1192
1260
|
// Precompute label-class permutations so numbering lookup doesn't rebuild arrays per caption.
|
|
1193
1261
|
opt.labelClassLookup = buildLabelClassLookup(opt)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-it-figure-with-p-caption",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.1",
|
|
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,10 +20,10 @@
|
|
|
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"
|