@peaceroad/markdown-it-figure-with-p-caption 0.16.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -17
- package/embeds/detect.js +182 -0
- package/embeds/providers.js +30 -0
- package/index.js +429 -344
- package/package.json +7 -6
package/index.js
CHANGED
|
@@ -1,42 +1,198 @@
|
|
|
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
|
-
const attrParseReg = /^(.*?)="?(.*)"?$/
|
|
14
16
|
const sampLangReg = /^ *(?:samp|shell|console)(?:(?= )|$)/
|
|
15
|
-
const endBlockquoteScriptReg = /<\/blockquote> *<script[^>]*?><\/script>$/
|
|
16
|
-
const iframeTagReg = /<iframe(?=[\s>])/i
|
|
17
17
|
const asciiLabelReg = /^[A-Za-z]/
|
|
18
|
+
const attrNameReg = /^[^\s=]+$/
|
|
19
|
+
const labelTrailingJointReg = /[.\u3002\uff0e::]\s*$/
|
|
18
20
|
const CHECK_TYPE_TOKEN_MAP = {
|
|
19
21
|
table_open: 'table',
|
|
20
22
|
pre_open: 'pre',
|
|
21
23
|
blockquote_open: 'blockquote',
|
|
22
24
|
}
|
|
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: '図' }
|
|
38
|
-
|
|
39
25
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
26
|
+
const normalizeLanguageCode = (value) => {
|
|
27
|
+
if (value === null || value === undefined) return ''
|
|
28
|
+
const normalized = String(value).trim().toLowerCase()
|
|
29
|
+
if (!normalized) return ''
|
|
30
|
+
const separatorIndex = normalized.search(/[-_]/)
|
|
31
|
+
return separatorIndex === -1 ? normalized : normalized.slice(0, separatorIndex)
|
|
32
|
+
}
|
|
33
|
+
const appendAvailableLanguage = (target, lang, availableLanguages) => {
|
|
34
|
+
if (!lang) return false
|
|
35
|
+
if (availableLanguages.indexOf(lang) === -1) return false
|
|
36
|
+
if (target.indexOf(lang) !== -1) return false
|
|
37
|
+
target.push(lang)
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
const normalizePreferredLanguages = (value, availableLanguages) => {
|
|
41
|
+
if (!Array.isArray(availableLanguages) || availableLanguages.length === 0) return []
|
|
42
|
+
const languages = []
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
appendAvailableLanguage(languages, normalizeLanguageCode(value), availableLanguages)
|
|
45
|
+
return languages
|
|
46
|
+
}
|
|
47
|
+
const source = Array.isArray(value) ? value : []
|
|
48
|
+
if (source.length === 0) return languages
|
|
49
|
+
for (let i = 0; i < source.length; i++) {
|
|
50
|
+
const lang = normalizeLanguageCode(source[i])
|
|
51
|
+
appendAvailableLanguage(languages, lang, availableLanguages)
|
|
52
|
+
}
|
|
53
|
+
return languages
|
|
54
|
+
}
|
|
55
|
+
const prioritizeLanguages = (languages, preferredLanguages) => {
|
|
56
|
+
if (!Array.isArray(languages) || languages.length === 0) return []
|
|
57
|
+
if (typeof preferredLanguages === 'string') {
|
|
58
|
+
if (!preferredLanguages || languages.indexOf(preferredLanguages) === -1) return languages
|
|
59
|
+
if (languages[0] === preferredLanguages) return languages
|
|
60
|
+
const prioritized = [preferredLanguages]
|
|
61
|
+
for (let i = 0; i < languages.length; i++) {
|
|
62
|
+
appendAvailableLanguage(prioritized, languages[i], languages)
|
|
63
|
+
}
|
|
64
|
+
return prioritized
|
|
65
|
+
}
|
|
66
|
+
if (!Array.isArray(preferredLanguages) || preferredLanguages.length === 0) return languages
|
|
67
|
+
const prioritized = []
|
|
68
|
+
for (let i = 0; i < preferredLanguages.length; i++) {
|
|
69
|
+
appendAvailableLanguage(prioritized, preferredLanguages[i], languages)
|
|
70
|
+
}
|
|
71
|
+
if (prioritized.length === 0) return languages
|
|
72
|
+
for (let i = 0; i < languages.length; i++) {
|
|
73
|
+
appendAvailableLanguage(prioritized, languages[i], languages)
|
|
74
|
+
}
|
|
75
|
+
return prioritized
|
|
76
|
+
}
|
|
77
|
+
const isAsciiAlphaCode = (code) => {
|
|
78
|
+
return (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a)
|
|
79
|
+
}
|
|
80
|
+
const isJapaneseCharCode = (code) => {
|
|
81
|
+
return (
|
|
82
|
+
(code >= 0x3040 && code <= 0x30ff) ||
|
|
83
|
+
(code >= 0x31f0 && code <= 0x31ff) ||
|
|
84
|
+
(code >= 0x4e00 && code <= 0x9fff) ||
|
|
85
|
+
(code >= 0xff66 && code <= 0xff9f)
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
const isHyphenFenceLine = (src, lineStart) => {
|
|
89
|
+
if (typeof src !== 'string' || lineStart < 0 || lineStart >= src.length) return 0
|
|
90
|
+
let index = lineStart
|
|
91
|
+
let hyphenCount = 0
|
|
92
|
+
while (index < src.length && src.charCodeAt(index) === 0x2d) {
|
|
93
|
+
hyphenCount++
|
|
94
|
+
index++
|
|
95
|
+
}
|
|
96
|
+
if (hyphenCount < 3) return 0
|
|
97
|
+
while (index < src.length && src.charCodeAt(index) === 0x20) {
|
|
98
|
+
index++
|
|
99
|
+
}
|
|
100
|
+
if (index >= src.length || src.charCodeAt(index) !== 0x0a) return 0
|
|
101
|
+
return hyphenCount
|
|
102
|
+
}
|
|
103
|
+
const skipLeadingFrontmatter = (src) => {
|
|
104
|
+
if (typeof src !== 'string' || isHyphenFenceLine(src, 0) === 0) return src
|
|
105
|
+
let lineStart = src.indexOf('\n')
|
|
106
|
+
if (lineStart === -1) return src
|
|
107
|
+
lineStart++
|
|
108
|
+
while (lineStart < src.length) {
|
|
109
|
+
if (isHyphenFenceLine(src, lineStart) > 0) {
|
|
110
|
+
const nextLineStart = src.indexOf('\n', lineStart)
|
|
111
|
+
if (nextLineStart === -1) return ''
|
|
112
|
+
return src.slice(nextLineStart + 1)
|
|
113
|
+
}
|
|
114
|
+
const nextLineStart = src.indexOf('\n', lineStart)
|
|
115
|
+
if (nextLineStart === -1) break
|
|
116
|
+
lineStart = nextLineStart + 1
|
|
117
|
+
}
|
|
118
|
+
return src
|
|
119
|
+
}
|
|
120
|
+
const detectDocumentPrimaryLanguage = (src, availableLanguages) => {
|
|
121
|
+
if (!src || availableLanguages.indexOf('ja') === -1) return ''
|
|
122
|
+
const body = skipLeadingFrontmatter(src)
|
|
123
|
+
const limit = Math.min(body.length, 8192)
|
|
124
|
+
let japaneseCount = 0
|
|
125
|
+
let asciiAlphaCount = 0
|
|
126
|
+
for (let i = 0; i < limit; i++) {
|
|
127
|
+
const code = body.charCodeAt(i)
|
|
128
|
+
if (isJapaneseCharCode(code)) {
|
|
129
|
+
japaneseCount++
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
if (isAsciiAlphaCode(code)) {
|
|
133
|
+
asciiAlphaCount++
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (japaneseCount === 0) return ''
|
|
137
|
+
if (asciiAlphaCount === 0) return 'ja'
|
|
138
|
+
return japaneseCount * 2 >= asciiAlphaCount ? 'ja' : ''
|
|
139
|
+
}
|
|
140
|
+
const sourceMayNeedPreferredLanguages = (state) => {
|
|
141
|
+
const src = state && typeof state.src === 'string' ? state.src : ''
|
|
142
|
+
return src.indexOf('![') !== -1
|
|
143
|
+
}
|
|
144
|
+
const resolvePreferredLanguagesForState = (state, opt) => {
|
|
145
|
+
const availableLanguages = (
|
|
146
|
+
opt &&
|
|
147
|
+
opt.markRegState &&
|
|
148
|
+
Array.isArray(opt.markRegState.languages)
|
|
149
|
+
) ? opt.markRegState.languages : []
|
|
150
|
+
if (availableLanguages.length === 0) return []
|
|
151
|
+
|
|
152
|
+
const optionLanguages = opt && Array.isArray(opt.normalizedOptionLanguages)
|
|
153
|
+
? opt.normalizedOptionLanguages
|
|
154
|
+
: []
|
|
155
|
+
const baseLanguages = optionLanguages.length > 0 ? optionLanguages : availableLanguages
|
|
156
|
+
const env = state && state.env ? state.env : null
|
|
157
|
+
|
|
158
|
+
const envLocale = normalizeLanguageCode(env && env.locale)
|
|
159
|
+
if (envLocale && baseLanguages.indexOf(envLocale) !== -1) {
|
|
160
|
+
return prioritizeLanguages(baseLanguages, envLocale)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const envPreferredLocales = normalizePreferredLanguages(env && env.preferredLocales, baseLanguages)
|
|
164
|
+
if (envPreferredLocales.length > 0) {
|
|
165
|
+
return prioritizeLanguages(baseLanguages, envPreferredLocales)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const explicitPreferred = opt && Array.isArray(opt.preferredLanguages)
|
|
169
|
+
? opt.preferredLanguages
|
|
170
|
+
: []
|
|
171
|
+
if (explicitPreferred.length > 0) {
|
|
172
|
+
return prioritizeLanguages(baseLanguages, explicitPreferred)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const envPreferred = normalizePreferredLanguages(env && env.preferredLanguages, baseLanguages)
|
|
176
|
+
if (envPreferred.length > 0) {
|
|
177
|
+
return prioritizeLanguages(baseLanguages, envPreferred)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const envLanguage = normalizeLanguageCode(env && (env.preferredLanguage || env.lang || env.language))
|
|
181
|
+
if (envLanguage && baseLanguages.indexOf(envLanguage) !== -1) {
|
|
182
|
+
return prioritizeLanguages(baseLanguages, envLanguage)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const detectedLanguage = detectDocumentPrimaryLanguage(state && state.src ? state.src : '', baseLanguages)
|
|
186
|
+
if (detectedLanguage) {
|
|
187
|
+
return prioritizeLanguages(baseLanguages, detectedLanguage)
|
|
188
|
+
}
|
|
189
|
+
return baseLanguages
|
|
190
|
+
}
|
|
191
|
+
const needsPreferredLanguagesResolution = (opt) => {
|
|
192
|
+
if (!opt || !opt.markRegState || !Array.isArray(opt.markRegState.languages)) return false
|
|
193
|
+
if (opt.markRegState.languages.length <= 1) return false
|
|
194
|
+
return opt.autoAltCaption === true || opt.autoTitleCaption === true
|
|
195
|
+
}
|
|
40
196
|
const normalizeOptionalClassName = (value) => {
|
|
41
197
|
if (value === null || value === undefined) return ''
|
|
42
198
|
const normalized = String(value).trim()
|
|
@@ -50,36 +206,6 @@ const normalizeClassOptionWithFallback = (value, fallbackValue) => {
|
|
|
50
206
|
const normalized = normalizeOptionalClassName(value)
|
|
51
207
|
return normalized || fallbackValue
|
|
52
208
|
}
|
|
53
|
-
const normalizeLanguages = (value) => {
|
|
54
|
-
if (!Array.isArray(value)) return ['en', 'ja']
|
|
55
|
-
const normalized = []
|
|
56
|
-
const seen = new Set()
|
|
57
|
-
for (let i = 0; i < value.length; i++) {
|
|
58
|
-
const lang = value[i]
|
|
59
|
-
if (typeof lang !== 'string') continue
|
|
60
|
-
const trimmed = lang.trim()
|
|
61
|
-
if (!trimmed || seen.has(trimmed)) continue
|
|
62
|
-
seen.add(trimmed)
|
|
63
|
-
normalized.push(trimmed)
|
|
64
|
-
}
|
|
65
|
-
if (normalized.length === 0) return ['en', 'ja']
|
|
66
|
-
return normalized
|
|
67
|
-
}
|
|
68
|
-
const normalizeLabelPrefixMarkers = (value) => {
|
|
69
|
-
if (typeof value === 'string') {
|
|
70
|
-
return value ? [value] : []
|
|
71
|
-
}
|
|
72
|
-
if (Array.isArray(value)) {
|
|
73
|
-
const normalized = value.map(entry => String(entry)).filter(Boolean)
|
|
74
|
-
return normalized.length > 2 ? normalized.slice(0, 2) : normalized
|
|
75
|
-
}
|
|
76
|
-
return []
|
|
77
|
-
}
|
|
78
|
-
const buildLabelPrefixMarkerRegFromList = (markers) => {
|
|
79
|
-
if (!markers || markers.length === 0) return null
|
|
80
|
-
const pattern = markers.map(escapeRegExp).join('|')
|
|
81
|
-
return new RegExp('^(?:' + pattern + ')(?:[ \\t ]+)?')
|
|
82
|
-
}
|
|
83
209
|
const resolveLabelPrefixMarkerPair = (markers) => {
|
|
84
210
|
if (!markers || markers.length === 0) return { prev: [], next: [] }
|
|
85
211
|
if (markers.length === 1) {
|
|
@@ -87,26 +213,6 @@ const resolveLabelPrefixMarkerPair = (markers) => {
|
|
|
87
213
|
}
|
|
88
214
|
return { prev: [markers[0]], next: [markers[1]] }
|
|
89
215
|
}
|
|
90
|
-
const stripLeadingPrefix = (text, prefix) => {
|
|
91
|
-
if (typeof text !== 'string' || !text || !prefix) return text
|
|
92
|
-
if (text.startsWith(prefix)) return text.slice(prefix.length)
|
|
93
|
-
return text
|
|
94
|
-
}
|
|
95
|
-
const stripLabelPrefixMarkerFromInline = (inlineToken, markerText) => {
|
|
96
|
-
if (!inlineToken || !markerText) return
|
|
97
|
-
if (typeof inlineToken.content === 'string') {
|
|
98
|
-
inlineToken.content = stripLeadingPrefix(inlineToken.content, markerText)
|
|
99
|
-
}
|
|
100
|
-
if (inlineToken.children && inlineToken.children.length) {
|
|
101
|
-
for (let i = 0; i < inlineToken.children.length; i++) {
|
|
102
|
-
const child = inlineToken.children[i]
|
|
103
|
-
if (child && child.type === 'text' && typeof child.content === 'string') {
|
|
104
|
-
child.content = stripLeadingPrefix(child.content, markerText)
|
|
105
|
-
break
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
216
|
const getLabelPrefixMarkerMatch = (inlineToken, markerReg) => {
|
|
111
217
|
if (!markerReg || !inlineToken || inlineToken.type !== 'inline') return null
|
|
112
218
|
const content = typeof inlineToken.content === 'string' ? inlineToken.content : ''
|
|
@@ -118,10 +224,62 @@ const getLabelPrefixMarkerMatch = (inlineToken, markerReg) => {
|
|
|
118
224
|
return match[0]
|
|
119
225
|
}
|
|
120
226
|
|
|
121
|
-
const
|
|
227
|
+
const splitImageAttrParts = (raw) => {
|
|
122
228
|
if (raw === null || raw === undefined) return null
|
|
229
|
+
const parts = []
|
|
230
|
+
let current = ''
|
|
231
|
+
let quote = ''
|
|
232
|
+
let escaped = false
|
|
233
|
+
for (let i = 0; i < raw.length; i++) {
|
|
234
|
+
const ch = raw[i]
|
|
235
|
+
if (quote) {
|
|
236
|
+
current += ch
|
|
237
|
+
if (escaped) {
|
|
238
|
+
escaped = false
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
if (ch === '\\') {
|
|
242
|
+
escaped = true
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
if (ch === quote) {
|
|
246
|
+
quote = ''
|
|
247
|
+
}
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
if (ch === '"' || ch === "'") {
|
|
251
|
+
quote = ch
|
|
252
|
+
current += ch
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
if (ch === ' ') {
|
|
256
|
+
if (current) {
|
|
257
|
+
parts.push(current)
|
|
258
|
+
current = ''
|
|
259
|
+
}
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
current += ch
|
|
263
|
+
}
|
|
264
|
+
if (quote) return null
|
|
265
|
+
if (current) parts.push(current)
|
|
266
|
+
return parts
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const unquoteAttrValue = (value) => {
|
|
270
|
+
if (typeof value !== 'string' || value.length < 2) return value || ''
|
|
271
|
+
const first = value[0]
|
|
272
|
+
const last = value[value.length - 1]
|
|
273
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
274
|
+
return value.slice(1, -1).replace(/\\(["'\\])/g, '$1')
|
|
275
|
+
}
|
|
276
|
+
return value
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const parseImageAttrs = (raw) => {
|
|
280
|
+
const parts = splitImageAttrParts(raw)
|
|
281
|
+
if (!parts || parts.length === 0) return null
|
|
123
282
|
const attrs = []
|
|
124
|
-
const parts = raw.split(/ +/)
|
|
125
283
|
for (let i = 0; i < parts.length; i++) {
|
|
126
284
|
let entry = parts[i]
|
|
127
285
|
if (!entry) continue
|
|
@@ -130,9 +288,15 @@ const parseImageAttrs = (raw) => {
|
|
|
130
288
|
} else if (idAttrReg.test(entry)) {
|
|
131
289
|
entry = entry.replace(idAttrReg, 'id=')
|
|
132
290
|
}
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
135
|
-
|
|
291
|
+
const equalIndex = entry.indexOf('=')
|
|
292
|
+
if (equalIndex === -1) {
|
|
293
|
+
if (!attrNameReg.test(entry)) return null
|
|
294
|
+
attrs.push([entry, ''])
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
const name = entry.slice(0, equalIndex)
|
|
298
|
+
if (!name || !attrNameReg.test(name)) return null
|
|
299
|
+
attrs.push([name, unquoteAttrValue(entry.slice(equalIndex + 1))])
|
|
136
300
|
}
|
|
137
301
|
return attrs
|
|
138
302
|
}
|
|
@@ -149,20 +313,6 @@ const normalizeAutoLabelNumberSets = (value) => {
|
|
|
149
313
|
return normalized
|
|
150
314
|
}
|
|
151
315
|
|
|
152
|
-
const buildLabelClassLookup = (opt) => {
|
|
153
|
-
const classPrefix = opt.classPrefix ? opt.classPrefix + '-' : ''
|
|
154
|
-
const defaultClasses = [classPrefix + 'label']
|
|
155
|
-
const withType = (type) => {
|
|
156
|
-
if (opt.removeMarkNameInCaptionClass) return defaultClasses
|
|
157
|
-
return [classPrefix + type + '-label', ...defaultClasses]
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
img: withType('img'),
|
|
161
|
-
table: withType('table'),
|
|
162
|
-
default: defaultClasses,
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
316
|
const shouldApplyLabelNumbering = (captionType, opt) => {
|
|
167
317
|
const setting = opt.autoLabelNumberSets
|
|
168
318
|
if (!setting) return false
|
|
@@ -241,60 +391,43 @@ const getImageAltText = (token) => {
|
|
|
241
391
|
|
|
242
392
|
const getImageTitleText = (token) => getTokenAttr(token, 'title')
|
|
243
393
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
const char = target[i]
|
|
249
|
-
const code = target.charCodeAt(i)
|
|
250
|
-
if (isJapaneseCharCode(code)) return 'ja'
|
|
251
|
-
if (isSentenceBoundaryChar(char) || char === '\n') break
|
|
394
|
+
const getFallbackStringLabelJoint = (label) => {
|
|
395
|
+
if (!label) return ''
|
|
396
|
+
if (labelTrailingJointReg.test(label)) {
|
|
397
|
+
return asciiLabelReg.test(label) ? ' ' : ''
|
|
252
398
|
}
|
|
253
|
-
return '
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const isJapaneseCharCode = (code) => {
|
|
257
|
-
return (
|
|
258
|
-
(code >= 0x3040 && code <= 0x30ff) || // Hiragana + Katakana
|
|
259
|
-
(code >= 0x31f0 && code <= 0x31ff) || // Katakana extensions
|
|
260
|
-
(code >= 0x4e00 && code <= 0x9fff) || // CJK Unified Ideographs
|
|
261
|
-
(code >= 0xff66 && code <= 0xff9f) // Half-width Katakana
|
|
262
|
-
)
|
|
399
|
+
return asciiLabelReg.test(label) ? '. ' : ' '
|
|
263
400
|
}
|
|
264
401
|
|
|
265
|
-
const
|
|
266
|
-
return char === '.' || char === '!' || char === '?' || char === '。' || char === '!' || char === '?'
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const getAutoFallbackLabel = (text) => {
|
|
270
|
-
const lang = detectCaptionLanguage(text)
|
|
271
|
-
if (lang === 'ja') return fallbackLabelDefaults.ja || fallbackLabelDefaults.en || ''
|
|
272
|
-
return fallbackLabelDefaults.en || fallbackLabelDefaults.ja || ''
|
|
273
|
-
}
|
|
274
|
-
|
|
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
|
|
280
|
-
return resolved
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const buildCaptionWithFallback = (text, fallbackOption, fallbackState) => {
|
|
402
|
+
const buildCaptionWithFallback = (text, fallbackOption, mark, markRegState, preferredLanguages) => {
|
|
284
403
|
const trimmedText = (text || '').trim()
|
|
285
404
|
if (!fallbackOption) return ''
|
|
405
|
+
if (!trimmedText) return ''
|
|
286
406
|
let label = ''
|
|
407
|
+
let generatedDefaults = null
|
|
287
408
|
if (typeof fallbackOption === 'string') {
|
|
288
409
|
label = fallbackOption.trim()
|
|
289
410
|
} else if (fallbackOption === true) {
|
|
290
|
-
|
|
411
|
+
generatedDefaults = getGeneratedLabelDefaults(mark, trimmedText, markRegState, preferredLanguages)
|
|
412
|
+
label = generatedDefaults && generatedDefaults.label ? generatedDefaults.label : ''
|
|
413
|
+
}
|
|
414
|
+
if (!label) return fallbackOption === true ? '' : trimmedText
|
|
415
|
+
if (generatedDefaults) {
|
|
416
|
+
return label + (generatedDefaults.joint || '') + (generatedDefaults.space || '') + trimmedText
|
|
291
417
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
418
|
+
return label + getFallbackStringLabelJoint(label) + trimmedText
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const validateFallbackCaptionLabelOption = (optionName, fallbackOption, markRegState) => {
|
|
422
|
+
if (typeof fallbackOption !== 'string') return
|
|
423
|
+
const sampleCaption = buildCaptionWithFallback('caption', fallbackOption, 'img', markRegState, null)
|
|
424
|
+
const analysis = analyzeCaptionStart(sampleCaption, {
|
|
425
|
+
markRegState,
|
|
426
|
+
preferredMark: 'img',
|
|
427
|
+
})
|
|
428
|
+
if (!analysis || analysis.mark !== 'img' || analysis.kind !== 'caption') {
|
|
429
|
+
throw new Error(`${optionName} must be a string label recognized as an image caption by p7d-markdown-it-p-captions: ${fallbackOption}`)
|
|
296
430
|
}
|
|
297
|
-
return label + (isAsciiLabel ? '. ' : ' ') + trimmedText
|
|
298
431
|
}
|
|
299
432
|
|
|
300
433
|
const createAutoCaptionParagraph = (captionText, TokenConstructor) => {
|
|
@@ -322,12 +455,18 @@ const getCaptionInlineToken = (tokens, range, caption) => {
|
|
|
322
455
|
}
|
|
323
456
|
|
|
324
457
|
const hasClassName = (classAttr, className) => {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
458
|
+
if (!classAttr || !className) return false
|
|
459
|
+
let index = 0
|
|
460
|
+
while (index < classAttr.length) {
|
|
461
|
+
index = classAttr.indexOf(className, index)
|
|
462
|
+
if (index === -1) return false
|
|
463
|
+
const end = index + className.length
|
|
464
|
+
const beforeBoundary = index === 0 || classAttr.charCodeAt(index - 1) <= 0x20
|
|
465
|
+
const afterBoundary = end >= classAttr.length || classAttr.charCodeAt(end) <= 0x20
|
|
466
|
+
if (beforeBoundary && afterBoundary) return true
|
|
467
|
+
index = end
|
|
468
|
+
}
|
|
469
|
+
return false
|
|
331
470
|
}
|
|
332
471
|
|
|
333
472
|
const hasAnyClassName = (classAttr, classNames) => {
|
|
@@ -420,154 +559,55 @@ const ensureAutoFigureNumbering = (tokens, range, caption, figureNumberState, op
|
|
|
420
559
|
updateInlineTokenContent(inlineToken, originalText, newLabelText)
|
|
421
560
|
}
|
|
422
561
|
|
|
423
|
-
const matchAutoCaptionText = (text,
|
|
424
|
-
if (!text || !
|
|
562
|
+
const matchAutoCaptionText = (text, opt, preferredMark = 'img') => {
|
|
563
|
+
if (!text || !opt || !opt.markRegState) return ''
|
|
425
564
|
const trimmed = text.trim()
|
|
426
|
-
if (trimmed
|
|
565
|
+
if (!trimmed) return ''
|
|
566
|
+
const analysis = analyzeCaptionStart(trimmed, {
|
|
567
|
+
markRegState: opt.markRegState,
|
|
568
|
+
preferredMark,
|
|
569
|
+
})
|
|
570
|
+
if (analysis) return trimmed
|
|
427
571
|
return ''
|
|
428
572
|
}
|
|
429
573
|
|
|
430
|
-
const getAutoCaptionFromImage = (imageToken, opt
|
|
431
|
-
const imgCaptionMarkReg = opt && opt.imgCaptionMarkReg ? opt.imgCaptionMarkReg : null
|
|
574
|
+
const getAutoCaptionFromImage = (imageToken, opt) => {
|
|
432
575
|
if (!opt.autoCaptionDetection) return ''
|
|
433
|
-
if (!
|
|
576
|
+
if (!opt.autoAltCaption && !opt.autoTitleCaption && !(opt.markRegState && opt.markRegState.markReg && opt.markRegState.markReg.img)) return ''
|
|
434
577
|
|
|
435
578
|
const altText = getImageAltText(imageToken)
|
|
436
|
-
let caption = matchAutoCaptionText(altText,
|
|
579
|
+
let caption = matchAutoCaptionText(altText, opt)
|
|
437
580
|
if (caption) {
|
|
438
581
|
clearImageAltAttr(imageToken)
|
|
439
582
|
return caption
|
|
440
583
|
}
|
|
441
584
|
if (!caption && opt.autoAltCaption) {
|
|
442
585
|
const altForFallback = altText || ''
|
|
443
|
-
|
|
444
|
-
if (imageToken) {
|
|
586
|
+
const fallbackCaption = buildCaptionWithFallback(altForFallback, opt.autoAltCaption, 'img', opt.markRegState, opt.preferredLanguages)
|
|
587
|
+
if (fallbackCaption && imageToken) {
|
|
445
588
|
clearImageAltAttr(imageToken)
|
|
446
589
|
}
|
|
590
|
+
caption = fallbackCaption
|
|
447
591
|
}
|
|
448
592
|
if (caption) return caption
|
|
449
593
|
|
|
450
594
|
const titleText = getImageTitleText(imageToken)
|
|
451
|
-
caption = matchAutoCaptionText(titleText,
|
|
595
|
+
caption = matchAutoCaptionText(titleText, opt)
|
|
452
596
|
if (caption) {
|
|
453
597
|
clearImageTitleAttr(imageToken)
|
|
454
598
|
return caption
|
|
455
599
|
}
|
|
456
600
|
if (!caption && opt.autoTitleCaption) {
|
|
457
601
|
const titleForFallback = titleText || ''
|
|
458
|
-
|
|
459
|
-
if (imageToken) {
|
|
602
|
+
const fallbackCaption = buildCaptionWithFallback(titleForFallback, opt.autoTitleCaption, 'img', opt.markRegState, opt.preferredLanguages)
|
|
603
|
+
if (fallbackCaption && imageToken) {
|
|
460
604
|
clearImageTitleAttr(imageToken)
|
|
461
605
|
}
|
|
606
|
+
caption = fallbackCaption
|
|
462
607
|
}
|
|
463
608
|
return caption
|
|
464
609
|
}
|
|
465
610
|
|
|
466
|
-
const getHtmlReg = (tag) => {
|
|
467
|
-
const cached = htmlRegCache.get(tag)
|
|
468
|
-
if (cached) return cached
|
|
469
|
-
const regexStr = `^<${tag} ?[^>]*?>[\\s\\S]*?<\\/${tag}>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)`
|
|
470
|
-
const reg = new RegExp(regexStr)
|
|
471
|
-
htmlRegCache.set(tag, reg)
|
|
472
|
-
return reg
|
|
473
|
-
}
|
|
474
|
-
|
|
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
611
|
const checkPrevCaption = (tokens, n, caption, sp, opt, captionState) => {
|
|
572
612
|
if(n < 3) return caption
|
|
573
613
|
const captionStartToken = tokens[n-3]
|
|
@@ -579,11 +619,11 @@ const checkPrevCaption = (tokens, n, caption, sp, opt, captionState) => {
|
|
|
579
619
|
const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
|
|
580
620
|
if(!captionName) {
|
|
581
621
|
if (opt.labelPrefixMarkerWithoutLabelPrevReg) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
622
|
+
const markerMatch = getLabelPrefixMarkerMatch(captionInlineToken, opt.labelPrefixMarkerWithoutLabelPrevReg)
|
|
623
|
+
if (markerMatch) {
|
|
624
|
+
stripLabelPrefixMarker(captionInlineToken, markerMatch)
|
|
625
|
+
caption.isPrev = true
|
|
626
|
+
}
|
|
587
627
|
}
|
|
588
628
|
return
|
|
589
629
|
}
|
|
@@ -603,11 +643,11 @@ const checkNextCaption = (tokens, en, caption, sp, opt, captionState) => {
|
|
|
603
643
|
const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
|
|
604
644
|
if(!captionName) {
|
|
605
645
|
if (opt.labelPrefixMarkerWithoutLabelNextReg) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
646
|
+
const markerMatch = getLabelPrefixMarkerMatch(captionInlineToken, opt.labelPrefixMarkerWithoutLabelNextReg)
|
|
647
|
+
if (markerMatch) {
|
|
648
|
+
stripLabelPrefixMarker(captionInlineToken, markerMatch)
|
|
649
|
+
caption.isNext = true
|
|
650
|
+
}
|
|
611
651
|
}
|
|
612
652
|
return
|
|
613
653
|
}
|
|
@@ -676,59 +716,117 @@ const changePrevCaptionPosition = (tokens, n, caption, opt) => {
|
|
|
676
716
|
const captionStartToken = tokens[n-3]
|
|
677
717
|
const captionInlineToken = tokens[n-2]
|
|
678
718
|
const captionEndToken = tokens[n-1]
|
|
719
|
+
const figureBaseLevel = getTokenLevel(tokens[n])
|
|
679
720
|
|
|
680
721
|
cleanCaptionTokenAttrs(captionStartToken, caption.name, opt)
|
|
681
722
|
captionStartToken.type = 'figcaption_open'
|
|
682
723
|
captionStartToken.tag = 'figcaption'
|
|
724
|
+
captionStartToken.block = true
|
|
725
|
+
captionStartToken.level = figureBaseLevel + 1
|
|
726
|
+
captionInlineToken.level = figureBaseLevel + 2
|
|
683
727
|
captionEndToken.type = 'figcaption_close'
|
|
684
728
|
captionEndToken.tag = 'figcaption'
|
|
729
|
+
captionEndToken.block = true
|
|
730
|
+
captionEndToken.level = figureBaseLevel + 1
|
|
685
731
|
tokens.splice(n + 2, 0, captionStartToken, captionInlineToken, captionEndToken)
|
|
686
732
|
tokens.splice(n-3, 3)
|
|
687
733
|
return true
|
|
688
734
|
}
|
|
689
735
|
|
|
690
736
|
const changeNextCaptionPosition = (tokens, en, caption, opt) => {
|
|
691
|
-
const captionStartToken = tokens[en+
|
|
692
|
-
const captionInlineToken = tokens[en+
|
|
693
|
-
const captionEndToken = tokens[en+
|
|
737
|
+
const captionStartToken = tokens[en+1]
|
|
738
|
+
const captionInlineToken = tokens[en+2]
|
|
739
|
+
const captionEndToken = tokens[en+3]
|
|
740
|
+
const figureBaseLevel = getTokenLevel(tokens[en])
|
|
694
741
|
cleanCaptionTokenAttrs(captionStartToken, caption.name, opt)
|
|
695
742
|
captionStartToken.type = 'figcaption_open'
|
|
696
743
|
captionStartToken.tag = 'figcaption'
|
|
744
|
+
captionStartToken.block = true
|
|
745
|
+
captionStartToken.level = figureBaseLevel + 1
|
|
746
|
+
captionInlineToken.level = figureBaseLevel + 2
|
|
697
747
|
captionEndToken.type = 'figcaption_close'
|
|
698
748
|
captionEndToken.tag = 'figcaption'
|
|
749
|
+
captionEndToken.block = true
|
|
750
|
+
captionEndToken.level = figureBaseLevel + 1
|
|
699
751
|
tokens.splice(en, 0, captionStartToken, captionInlineToken, captionEndToken)
|
|
700
|
-
tokens.splice(en+
|
|
752
|
+
tokens.splice(en+4, 3)
|
|
701
753
|
return true
|
|
702
754
|
}
|
|
703
755
|
|
|
756
|
+
const getTokenMap = (token) => {
|
|
757
|
+
return token && Array.isArray(token.map) && token.map.length === 2 ? token.map : null
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const findNearestMapInRange = (tokens, start, end, step) => {
|
|
761
|
+
let i = start
|
|
762
|
+
while (step > 0 ? i <= end : i >= end) {
|
|
763
|
+
const map = getTokenMap(tokens[i])
|
|
764
|
+
if (map) return map
|
|
765
|
+
i += step
|
|
766
|
+
}
|
|
767
|
+
return null
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const getRangeMap = (tokens, start, end) => {
|
|
771
|
+
const startMap = getTokenMap(tokens[start]) || findNearestMapInRange(tokens, start, end, 1)
|
|
772
|
+
if (!startMap) return null
|
|
773
|
+
const endMap = getTokenMap(tokens[end]) || findNearestMapInRange(tokens, end, start, -1) || startMap
|
|
774
|
+
const startLine = startMap[0]
|
|
775
|
+
const endLine = Math.max(startMap[1], endMap[1])
|
|
776
|
+
if (typeof startLine !== 'number' || typeof endLine !== 'number' || endLine < startLine) {
|
|
777
|
+
return [startMap[0], startMap[1]]
|
|
778
|
+
}
|
|
779
|
+
return [startLine, endLine]
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const getTokenLevel = (token, fallback = 0) => {
|
|
783
|
+
return token && typeof token.level === 'number' ? token.level : fallback
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const adjustTokenLevels = (tokens, start, end, delta) => {
|
|
787
|
+
if (!delta) return
|
|
788
|
+
for (let i = start; i <= end; i++) {
|
|
789
|
+
const token = tokens[i]
|
|
790
|
+
if (token && typeof token.level === 'number') {
|
|
791
|
+
token.level += delta
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
704
796
|
const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInsteadOfWrap, sp, opt, TokenConstructor) => {
|
|
705
797
|
let n = range.start
|
|
706
798
|
let en = range.end
|
|
799
|
+
const baseLevel = getTokenLevel(tokens[n])
|
|
800
|
+
const childLevel = baseLevel + 1
|
|
707
801
|
const figureStartToken = new TokenConstructor('figure_open', 'figure', 1)
|
|
708
802
|
const figureClassName = sp.figureClassName || resolveFigureClassName(checkTokenTagName, sp, opt)
|
|
709
803
|
figureStartToken.attrSet('class', figureClassName)
|
|
804
|
+
figureStartToken.block = true
|
|
805
|
+
figureStartToken.level = baseLevel
|
|
710
806
|
|
|
711
807
|
if (opt.roleDocExample && (checkTokenTagName === 'pre-code' || checkTokenTagName === 'pre-samp')) {
|
|
712
808
|
figureStartToken.attrSet('role', 'doc-example')
|
|
713
809
|
}
|
|
714
810
|
const figureEndToken = new TokenConstructor('figure_close', 'figure', -1)
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
if (rangeStartMap) {
|
|
722
|
-
figureStartToken.map = [rangeStartMap[0], rangeStartMap[1]]
|
|
723
|
-
}
|
|
724
|
-
if (rangeEndMap) {
|
|
725
|
-
figureEndToken.map = [rangeEndMap[0], rangeEndMap[1]]
|
|
811
|
+
figureEndToken.block = true
|
|
812
|
+
figureEndToken.level = baseLevel
|
|
813
|
+
const rangeMap = getRangeMap(tokens, n, en)
|
|
814
|
+
if (rangeMap) {
|
|
815
|
+
figureStartToken.map = [rangeMap[0], rangeMap[1]]
|
|
816
|
+
figureEndToken.map = [rangeMap[0], rangeMap[1]]
|
|
726
817
|
}
|
|
727
818
|
const createBreakToken = () => {
|
|
728
819
|
const breakToken = new TokenConstructor('text', '', 0)
|
|
729
820
|
breakToken.content = '\n'
|
|
821
|
+
breakToken.level = childLevel
|
|
730
822
|
return breakToken
|
|
731
823
|
}
|
|
824
|
+
const createEmptyTextToken = () => {
|
|
825
|
+
const emptyToken = new TokenConstructor('text', '', 0)
|
|
826
|
+
emptyToken.content = ''
|
|
827
|
+
emptyToken.level = childLevel
|
|
828
|
+
return emptyToken
|
|
829
|
+
}
|
|
732
830
|
if (caption.name === 'img') {
|
|
733
831
|
const joinAttrs = (attrs) => {
|
|
734
832
|
if (!attrs || attrs.length === 0) return
|
|
@@ -743,12 +841,13 @@ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInstea
|
|
|
743
841
|
joinAttrs(tokens[n].attrs)
|
|
744
842
|
}
|
|
745
843
|
if (replaceInsteadOfWrap) {
|
|
746
|
-
tokens.splice(en, 1, createBreakToken(), figureEndToken
|
|
747
|
-
tokens.splice(n, 1, figureStartToken,
|
|
844
|
+
tokens.splice(en, 1, createBreakToken(), figureEndToken)
|
|
845
|
+
tokens.splice(n, 1, figureStartToken, createEmptyTextToken())
|
|
748
846
|
en = en + 2
|
|
749
847
|
} else {
|
|
750
|
-
tokens
|
|
751
|
-
tokens.splice(
|
|
848
|
+
adjustTokenLevels(tokens, n, en, 1)
|
|
849
|
+
tokens.splice(en+1, 0, figureEndToken)
|
|
850
|
+
tokens.splice(n, 0, figureStartToken, createEmptyTextToken())
|
|
752
851
|
en = en + 3
|
|
753
852
|
}
|
|
754
853
|
range.start = n
|
|
@@ -852,38 +951,6 @@ const detectFenceToken = (token, n, caption) => {
|
|
|
852
951
|
}
|
|
853
952
|
}
|
|
854
953
|
|
|
855
|
-
const detectHtmlBlockToken = (tokens, token, n, caption, sp, opt) => {
|
|
856
|
-
if (!token || token.type !== 'html_block') return null
|
|
857
|
-
const hints = getHtmlDetectionHints(token.content)
|
|
858
|
-
if (!hasAnyHtmlDetectionHint(hints)) return null
|
|
859
|
-
let matchedTag = ''
|
|
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
|
|
863
|
-
}
|
|
864
|
-
if (!matchedTag) return null
|
|
865
|
-
if (matchedTag === 'blockquote') {
|
|
866
|
-
if (isIframeTypeEmbedBlockquote(token.content)) {
|
|
867
|
-
sp.isIframeTypeBlockquote = true
|
|
868
|
-
} else {
|
|
869
|
-
return null
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
if (matchedTag === 'iframe' && videoIframeReg.test(token.content)) {
|
|
873
|
-
sp.isVideoIframe = true
|
|
874
|
-
}
|
|
875
|
-
caption.name = matchedTag
|
|
876
|
-
const wrapWithoutCaption = resolveHtmlWrapWithoutCaption(matchedTag, sp, opt)
|
|
877
|
-
return {
|
|
878
|
-
type: 'html',
|
|
879
|
-
tagName: matchedTag,
|
|
880
|
-
en: n,
|
|
881
|
-
replaceInsteadOfWrap: false,
|
|
882
|
-
wrapWithoutCaption,
|
|
883
|
-
canWrap: true,
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
954
|
const hasLeadingImageChild = (token) => {
|
|
888
955
|
return !!(token &&
|
|
889
956
|
token.type === 'inline' &&
|
|
@@ -896,7 +963,7 @@ const hasLeadingImageChild = (token) => {
|
|
|
896
963
|
const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
897
964
|
const multipleImagesEnabled = !!opt.multipleImages
|
|
898
965
|
const styleProcessEnabled = !!opt.styleProcess
|
|
899
|
-
const
|
|
966
|
+
const allowImageParagraphWithoutCaption = !!opt.imageOnlyParagraphWithoutCaption
|
|
900
967
|
const children = nextToken.children
|
|
901
968
|
const imageToken = children[0]
|
|
902
969
|
const childrenLength = children.length
|
|
@@ -911,7 +978,7 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
911
978
|
tagName: 'img',
|
|
912
979
|
en: n + 2,
|
|
913
980
|
replaceInsteadOfWrap: true,
|
|
914
|
-
wrapWithoutCaption:
|
|
981
|
+
wrapWithoutCaption: allowImageParagraphWithoutCaption,
|
|
915
982
|
canWrap: true,
|
|
916
983
|
imageToken,
|
|
917
984
|
}
|
|
@@ -935,13 +1002,13 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
935
1002
|
const imageAttrs = rawContent.match(imageAttrsReg)
|
|
936
1003
|
if (imageAttrs) {
|
|
937
1004
|
const parsedAttrs = parseImageAttrs(imageAttrs[1])
|
|
938
|
-
if (parsedAttrs
|
|
1005
|
+
if (parsedAttrs) {
|
|
939
1006
|
for (let i = 0; i < parsedAttrs.length; i++) {
|
|
940
1007
|
sp.attrs.push(parsedAttrs[i])
|
|
941
1008
|
}
|
|
942
1009
|
child.content = ''
|
|
1010
|
+
break
|
|
943
1011
|
}
|
|
944
|
-
break
|
|
945
1012
|
}
|
|
946
1013
|
}
|
|
947
1014
|
if (typeof rawContent === 'string' && rawContent.trim()) {
|
|
@@ -992,7 +1059,7 @@ const detectImageParagraph = (nextToken, n, caption, sp, opt) => {
|
|
|
992
1059
|
tagName,
|
|
993
1060
|
en,
|
|
994
1061
|
replaceInsteadOfWrap: true,
|
|
995
|
-
wrapWithoutCaption: isValid &&
|
|
1062
|
+
wrapWithoutCaption: isValid && allowImageParagraphWithoutCaption,
|
|
996
1063
|
canWrap: isValid,
|
|
997
1064
|
imageToken,
|
|
998
1065
|
}
|
|
@@ -1004,15 +1071,19 @@ const figureWithCaption = (state, opt) => {
|
|
|
1004
1071
|
table: 0,
|
|
1005
1072
|
}
|
|
1006
1073
|
|
|
1007
|
-
const fallbackLabelState = {
|
|
1008
|
-
img: null,
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
1074
|
const captionState = { tokens: state.tokens, Token: state.Token }
|
|
1012
|
-
|
|
1075
|
+
const shouldResolvePreferredLanguages = !!(
|
|
1076
|
+
opt.shouldResolvePreferredLanguages &&
|
|
1077
|
+
sourceMayNeedPreferredLanguages(state)
|
|
1078
|
+
)
|
|
1079
|
+
const renderOpt = shouldResolvePreferredLanguages ? Object.create(opt) : opt
|
|
1080
|
+
if (shouldResolvePreferredLanguages) {
|
|
1081
|
+
renderOpt.preferredLanguages = resolvePreferredLanguagesForState(state, opt)
|
|
1082
|
+
}
|
|
1083
|
+
figureWithCaptionCore(state.tokens, renderOpt, figureNumberState, state.Token, captionState, null, 0)
|
|
1013
1084
|
}
|
|
1014
1085
|
|
|
1015
|
-
const figureWithCaptionCore = (tokens, opt, figureNumberState,
|
|
1086
|
+
const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor, captionState, parentType = null, startIndex = 0) => {
|
|
1016
1087
|
const rRange = { start: startIndex, end: startIndex }
|
|
1017
1088
|
const rCaption = {
|
|
1018
1089
|
name: '', nameSuffix: '', isPrev: false, isNext: false
|
|
@@ -1030,7 +1101,7 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, fallbackLabelStat
|
|
|
1030
1101
|
const containerType = getNestedContainerType(token)
|
|
1031
1102
|
|
|
1032
1103
|
if (containerType && containerType !== 'blockquote') {
|
|
1033
|
-
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState,
|
|
1104
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, n + 1)
|
|
1034
1105
|
n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
|
|
1035
1106
|
continue
|
|
1036
1107
|
}
|
|
@@ -1055,7 +1126,12 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, fallbackLabelStat
|
|
|
1055
1126
|
resetRangeState(rRange, n)
|
|
1056
1127
|
resetCaptionState(rCaption)
|
|
1057
1128
|
resetSpecialState(rSp)
|
|
1058
|
-
detection =
|
|
1129
|
+
detection = detectHtmlFigureCandidate(tokens, token, n, opt.htmlWrapWithoutCaption)
|
|
1130
|
+
if (detection) {
|
|
1131
|
+
rCaption.name = detection.tagName
|
|
1132
|
+
rSp.isVideoIframe = !!detection.isVideoIframe
|
|
1133
|
+
rSp.isIframeTypeBlockquote = !!detection.isIframeTypeBlockquote
|
|
1134
|
+
}
|
|
1059
1135
|
} else if (tokenType === 'fence') {
|
|
1060
1136
|
resetRangeState(rRange, n)
|
|
1061
1137
|
resetCaptionState(rCaption)
|
|
@@ -1070,7 +1146,7 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, fallbackLabelStat
|
|
|
1070
1146
|
|
|
1071
1147
|
if (!detection) {
|
|
1072
1148
|
if (containerType === 'blockquote') {
|
|
1073
|
-
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState,
|
|
1149
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, n + 1)
|
|
1074
1150
|
n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
|
|
1075
1151
|
} else {
|
|
1076
1152
|
n++
|
|
@@ -1087,7 +1163,7 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, fallbackLabelStat
|
|
|
1087
1163
|
let hasCaption = rCaption.isPrev || rCaption.isNext
|
|
1088
1164
|
let pendingAutoCaption = ''
|
|
1089
1165
|
if (!hasCaption && detection.type === 'image' && opt.autoCaptionDetection) {
|
|
1090
|
-
pendingAutoCaption = getAutoCaptionFromImage(detection.imageToken, opt
|
|
1166
|
+
pendingAutoCaption = getAutoCaptionFromImage(detection.imageToken, opt)
|
|
1091
1167
|
if (pendingAutoCaption) {
|
|
1092
1168
|
hasCaption = true
|
|
1093
1169
|
}
|
|
@@ -1096,7 +1172,7 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, fallbackLabelStat
|
|
|
1096
1172
|
if (detection.canWrap === false) {
|
|
1097
1173
|
let nextIndex = rRange.end + 1
|
|
1098
1174
|
if (containerType === 'blockquote') {
|
|
1099
|
-
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState,
|
|
1175
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, rRange.start + 1)
|
|
1100
1176
|
nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : rRange.end) + 1)
|
|
1101
1177
|
}
|
|
1102
1178
|
n = nextIndex
|
|
@@ -1151,7 +1227,7 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, fallbackLabelStat
|
|
|
1151
1227
|
}
|
|
1152
1228
|
|
|
1153
1229
|
if (containerType === 'blockquote') {
|
|
1154
|
-
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState,
|
|
1230
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, rRange.start + 1)
|
|
1155
1231
|
const fallbackIndex = rCaption.name ? rRange.end : n
|
|
1156
1232
|
nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : fallbackIndex) + 1)
|
|
1157
1233
|
}
|
|
@@ -1165,13 +1241,15 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1165
1241
|
let opt = {
|
|
1166
1242
|
// Caption languages delegated to p-captions.
|
|
1167
1243
|
languages: ['en', 'ja'],
|
|
1244
|
+
preferredLanguages: null, // compatibility tie-break for generated fallback labels; prefer env.locale / env.preferredLocales per render
|
|
1168
1245
|
|
|
1169
1246
|
// --- figure-wrapper behavior ---
|
|
1170
1247
|
classPrefix: 'f',
|
|
1171
1248
|
figureClassThatWrapsIframeTypeBlockquote: null,
|
|
1172
1249
|
figureClassThatWrapsSlides: null,
|
|
1173
1250
|
styleProcess: true,
|
|
1174
|
-
|
|
1251
|
+
imageOnlyParagraphWithoutCaption: false,
|
|
1252
|
+
oneImageWithoutCaption: false, // legacy alias for imageOnlyParagraphWithoutCaption
|
|
1175
1253
|
iframeWithoutCaption: false,
|
|
1176
1254
|
videoWithoutCaption: false,
|
|
1177
1255
|
audioWithoutCaption: false,
|
|
@@ -1210,20 +1288,27 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1210
1288
|
figureToLabelClassMap: null,
|
|
1211
1289
|
}
|
|
1212
1290
|
const hasExplicitAutoLabelNumberSets = option && Object.prototype.hasOwnProperty.call(option, 'autoLabelNumberSets')
|
|
1291
|
+
const hasExplicitImageOnlyParagraphWithoutCaption = option && Object.prototype.hasOwnProperty.call(option, 'imageOnlyParagraphWithoutCaption')
|
|
1213
1292
|
const hasExplicitFigureClassThatWrapsIframeTypeBlockquote = option && Object.prototype.hasOwnProperty.call(option, 'figureClassThatWrapsIframeTypeBlockquote')
|
|
1214
1293
|
const hasExplicitFigureClassThatWrapsSlides = option && Object.prototype.hasOwnProperty.call(option, 'figureClassThatWrapsSlides')
|
|
1215
1294
|
const hasExplicitLabelClassFollowsFigure = option && Object.prototype.hasOwnProperty.call(option, 'labelClassFollowsFigure')
|
|
1216
1295
|
if (option) Object.assign(opt, option)
|
|
1296
|
+
opt.imageOnlyParagraphWithoutCaption = hasExplicitImageOnlyParagraphWithoutCaption
|
|
1297
|
+
? !!opt.imageOnlyParagraphWithoutCaption
|
|
1298
|
+
: !!opt.oneImageWithoutCaption
|
|
1299
|
+
opt.oneImageWithoutCaption = !!opt.oneImageWithoutCaption
|
|
1217
1300
|
if (!hasExplicitLabelClassFollowsFigure && opt.figureToLabelClassMap) {
|
|
1218
1301
|
opt.labelClassFollowsFigure = true
|
|
1219
1302
|
}
|
|
1220
1303
|
opt.classPrefix = normalizeOptionalClassName(opt.classPrefix)
|
|
1221
1304
|
opt.allIframeTypeFigureClassName = normalizeOptionalClassName(opt.allIframeTypeFigureClassName)
|
|
1222
|
-
opt.languages = normalizeLanguages(opt.languages)
|
|
1223
1305
|
opt.markRegState = getMarkRegStateForLanguages(opt.languages)
|
|
1224
|
-
opt.
|
|
1225
|
-
|
|
1226
|
-
|
|
1306
|
+
opt.preferredLanguages = normalizePreferredLanguages(opt.preferredLanguages, opt.markRegState.languages)
|
|
1307
|
+
if (opt.preferredLanguages.length === 0) opt.preferredLanguages = null
|
|
1308
|
+
opt.normalizedOptionLanguages = normalizePreferredLanguages(opt.languages, opt.markRegState.languages)
|
|
1309
|
+
opt.shouldResolvePreferredLanguages = needsPreferredLanguagesResolution(opt)
|
|
1310
|
+
validateFallbackCaptionLabelOption('autoAltCaption', opt.autoAltCaption, opt.markRegState)
|
|
1311
|
+
validateFallbackCaptionLabelOption('autoTitleCaption', opt.autoTitleCaption, opt.markRegState)
|
|
1227
1312
|
opt.htmlWrapWithoutCaption = {
|
|
1228
1313
|
iframe: !!opt.iframeWithoutCaption,
|
|
1229
1314
|
video: !!opt.videoWithoutCaption,
|
|
@@ -1260,12 +1345,12 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
1260
1345
|
// Precompute label-class permutations so numbering lookup doesn't rebuild arrays per caption.
|
|
1261
1346
|
opt.labelClassLookup = buildLabelClassLookup(opt)
|
|
1262
1347
|
const markerList = normalizeLabelPrefixMarkers(opt.labelPrefixMarker)
|
|
1263
|
-
opt.labelPrefixMarkerReg =
|
|
1348
|
+
opt.labelPrefixMarkerReg = buildLabelPrefixMarkerRegFromMarkers(markerList)
|
|
1264
1349
|
opt.cleanCaptionRegCache = new Map()
|
|
1265
1350
|
if (opt.allowLabelPrefixMarkerWithoutLabel === true) {
|
|
1266
1351
|
const markerPair = resolveLabelPrefixMarkerPair(markerList)
|
|
1267
|
-
opt.labelPrefixMarkerWithoutLabelPrevReg =
|
|
1268
|
-
opt.labelPrefixMarkerWithoutLabelNextReg =
|
|
1352
|
+
opt.labelPrefixMarkerWithoutLabelPrevReg = buildLabelPrefixMarkerRegFromMarkers(markerPair.prev)
|
|
1353
|
+
opt.labelPrefixMarkerWithoutLabelNextReg = buildLabelPrefixMarkerRegFromMarkers(markerPair.next)
|
|
1269
1354
|
} else {
|
|
1270
1355
|
opt.labelPrefixMarkerWithoutLabelPrevReg = null
|
|
1271
1356
|
opt.labelPrefixMarkerWithoutLabelNextReg = null
|