@peaceroad/markdown-it-figure-with-p-caption 0.12.0 → 0.14.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 +294 -274
- package/index.js +812 -362
- package/package.json +10 -4
- package/.vscode/settings.json +0 -5
- package/imgAttrToPCaption.js +0 -82
- package/test/examples-all-iframe-type-figure-class-name.txt +0 -192
- package/test/examples-console.txt +0 -125
- package/test/examples-has-num-class.txt +0 -31
- package/test/examples-iframe-type-blockquote-without-caption.txt +0 -92
- package/test/examples-iframe-without-caption.txt +0 -64
- package/test/examples-img-alt-caption-number.en.txt +0 -51
- package/test/examples-img-alt-caption.en.txt +0 -60
- package/test/examples-img-alt-caption.ja.txt +0 -84
- package/test/examples-img-title-caption-number.en.txt +0 -60
- package/test/examples-img-title-caption.en.txt +0 -60
- package/test/examples-img-title-caption.ja.txt +0 -30
- package/test/examples-multiple-images.txt +0 -140
- package/test/examples-no-option.txt +0 -770
- package/test/examples-one-image-without-caption.txt +0 -59
- package/test/examples-set-figure-number.en.txt +0 -21
- package/test/examples-video-without-caption.txt +0 -52
- package/test/test.js +0 -208
package/index.js
CHANGED
|
@@ -1,15 +1,316 @@
|
|
|
1
|
-
import { setCaptionParagraph } from 'p7d-markdown-it-p-captions'
|
|
2
|
-
import { imgAttrToPCaption, setAltToLabel, setTitleToLabel } from './imgAttrToPCaption.js'
|
|
1
|
+
import { setCaptionParagraph, markReg } from 'p7d-markdown-it-p-captions'
|
|
3
2
|
|
|
4
|
-
const htmlRegCache =
|
|
3
|
+
const htmlRegCache = new Map()
|
|
4
|
+
const cleanCaptionRegCache = new Map()
|
|
5
5
|
const classReg = /^f-(.+)$/
|
|
6
6
|
const blueskyEmbedReg = /^<blockquote class="bluesky-embed"[^]*?>[\s\S]*?$/
|
|
7
|
+
const videoIframeReg = /^<[^>]*? src="https:\/\/(?:www.youtube-nocookie.com|player.vimeo.com)\//i
|
|
8
|
+
const classNameReg = /^<[^>]*? class="(twitter-tweet|instagram-media|text-post-media|bluesky-embed|mastodon-embed)"/
|
|
9
|
+
const imageAttrsReg = /^ *\{(.*?)\} *$/
|
|
10
|
+
const classAttrReg = /^\./
|
|
11
|
+
const idAttrReg = /^#/
|
|
12
|
+
const attrParseReg = /^(.*?)="?(.*)"?$/
|
|
13
|
+
const sampLangReg = /^ *(?:samp|shell|console)(?:(?= )|$)/
|
|
14
|
+
const endBlockquoteScriptReg = /<\/blockquote> *<script[^>]*?><\/script>$/
|
|
15
|
+
const imgCaptionMarkReg = markReg && markReg.img ? markReg.img : null
|
|
16
|
+
const asciiLabelReg = /^[A-Za-z]/
|
|
17
|
+
const trailingDigitsReg = /(\d+)\s*$/
|
|
18
|
+
const CHECK_TYPE_TOKEN_MAP = {
|
|
19
|
+
table_open: 'table',
|
|
20
|
+
pre_open: 'pre',
|
|
21
|
+
blockquote_open: 'blockquote',
|
|
22
|
+
}
|
|
23
|
+
const HTML_TAG_CANDIDATES = ['video', 'audio', 'iframe', 'blockquote', 'div']
|
|
24
|
+
const fallbackLabelDefaults = {
|
|
25
|
+
img: { en: 'Figure', ja: '図' },
|
|
26
|
+
table: { en: 'Table', ja: '表' },
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const normalizeSetLabelNumbers = (value) => {
|
|
30
|
+
const normalized = { img: false, table: false }
|
|
31
|
+
if (!value) return normalized
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
for (const entry of value) {
|
|
34
|
+
if (normalized.hasOwnProperty(entry)) normalized[entry] = true
|
|
35
|
+
}
|
|
36
|
+
return normalized
|
|
37
|
+
}
|
|
38
|
+
return normalized
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const buildLabelClassLookup = (opt) => {
|
|
42
|
+
const classPrefix = opt.classPrefix ? opt.classPrefix + '-' : ''
|
|
43
|
+
const defaultClasses = [classPrefix + 'label']
|
|
44
|
+
const withType = (type) => {
|
|
45
|
+
if (opt.removeMarkNameInCaptionClass) return defaultClasses
|
|
46
|
+
return [classPrefix + type + '-label', ...defaultClasses]
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
img: withType('img'),
|
|
50
|
+
table: withType('table'),
|
|
51
|
+
default: defaultClasses,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const shouldApplyLabelNumbering = (captionType, opt) => {
|
|
56
|
+
const setting = opt.setLabelNumbers
|
|
57
|
+
if (!setting) return false
|
|
58
|
+
return !!setting[captionType]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const isOnlySpacesText = (token) => {
|
|
62
|
+
if (!token || token.type !== 'text') return false
|
|
63
|
+
const content = token.content
|
|
64
|
+
if (typeof content !== 'string') return false
|
|
65
|
+
for (let i = 0; i < content.length; i++) {
|
|
66
|
+
if (content.charCodeAt(i) !== 0x20) return false
|
|
67
|
+
}
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const getTokenAttr = (token, attrName) => {
|
|
72
|
+
if (!token || !token.attrs) return ''
|
|
73
|
+
for (let i = 0; i < token.attrs.length; i++) {
|
|
74
|
+
if (token.attrs[i][0] === attrName) return token.attrs[i][1] || ''
|
|
75
|
+
}
|
|
76
|
+
return ''
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const setTokenAttr = (token, attrName, value) => {
|
|
80
|
+
if (!token) return
|
|
81
|
+
if (!token.attrs) token.attrs = []
|
|
82
|
+
for (let i = 0; i < token.attrs.length; i++) {
|
|
83
|
+
if (token.attrs[i][0] === attrName) {
|
|
84
|
+
token.attrs[i][1] = value
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
token.attrs.push([attrName, value])
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const removeTokenAttr = (token, attrName) => {
|
|
92
|
+
if (!token || !token.attrs) return
|
|
93
|
+
for (let i = token.attrs.length - 1; i >= 0; i--) {
|
|
94
|
+
if (token.attrs[i][0] === attrName) {
|
|
95
|
+
token.attrs.splice(i, 1)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const clearImageAltAttr = (token) => {
|
|
101
|
+
if (!token) return
|
|
102
|
+
setTokenAttr(token, 'alt', '')
|
|
103
|
+
token.content = ''
|
|
104
|
+
if (token.children) {
|
|
105
|
+
for (let i = 0; i < token.children.length; i++) {
|
|
106
|
+
token.children[i].content = ''
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const clearImageTitleAttr = (token) => {
|
|
112
|
+
removeTokenAttr(token, 'title')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const getImageAltText = (token) => {
|
|
116
|
+
let alt = getTokenAttr(token, 'alt')
|
|
117
|
+
if (alt) return alt
|
|
118
|
+
if (typeof token.content === 'string' && token.content !== '') return token.content
|
|
119
|
+
if (token.children && token.children.length > 0) {
|
|
120
|
+
return token.children.map(child => child.content || '').join('')
|
|
121
|
+
}
|
|
122
|
+
return ''
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const getImageTitleText = (token) => getTokenAttr(token, 'title')
|
|
126
|
+
|
|
127
|
+
const detectCaptionLanguage = (text) => {
|
|
128
|
+
const target = (text || '').trim()
|
|
129
|
+
if (!target) return 'en'
|
|
130
|
+
for (let i = 0; i < target.length; i++) {
|
|
131
|
+
const char = target[i]
|
|
132
|
+
const code = target.charCodeAt(i)
|
|
133
|
+
if (isJapaneseCharCode(code)) return 'ja'
|
|
134
|
+
if (isSentenceBoundaryChar(char) || char === '\n') break
|
|
135
|
+
}
|
|
136
|
+
return 'en'
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const isJapaneseCharCode = (code) => {
|
|
140
|
+
return (
|
|
141
|
+
(code >= 0x3040 && code <= 0x30ff) || // Hiragana + Katakana
|
|
142
|
+
(code >= 0x31f0 && code <= 0x31ff) || // Katakana extensions
|
|
143
|
+
(code >= 0x4e00 && code <= 0x9fff) || // CJK Unified Ideographs
|
|
144
|
+
(code >= 0xff66 && code <= 0xff9f) // Half-width Katakana
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const isSentenceBoundaryChar = (char) => {
|
|
149
|
+
return char === '.' || char === '!' || char === '?' || char === '。' || char === '!' || char === '?'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const getAutoFallbackLabel = (text, captionType) => {
|
|
153
|
+
const type = captionType === 'table' ? 'table' : 'img'
|
|
154
|
+
const lang = detectCaptionLanguage(text)
|
|
155
|
+
const defaults = fallbackLabelDefaults[type] || fallbackLabelDefaults.img
|
|
156
|
+
if (lang === 'ja') return defaults.ja || defaults.en || ''
|
|
157
|
+
return defaults.en || defaults.ja || ''
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const getPersistedFallbackLabel = (text, captionType, fallbackState) => {
|
|
161
|
+
const type = captionType === 'table' ? 'table' : 'img'
|
|
162
|
+
if (!fallbackState) return getAutoFallbackLabel(text, type)
|
|
163
|
+
if (fallbackState[type]) return fallbackState[type]
|
|
164
|
+
const resolved = getAutoFallbackLabel(text, type)
|
|
165
|
+
fallbackState[type] = resolved
|
|
166
|
+
return resolved
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const buildCaptionWithFallback = (text, fallbackOption, captionType = 'img', fallbackState) => {
|
|
170
|
+
const trimmedText = (text || '').trim()
|
|
171
|
+
if (!fallbackOption) return ''
|
|
172
|
+
let label = ''
|
|
173
|
+
if (typeof fallbackOption === 'string') {
|
|
174
|
+
label = fallbackOption.trim()
|
|
175
|
+
} else if (fallbackOption === true) {
|
|
176
|
+
label = getPersistedFallbackLabel(trimmedText, captionType, fallbackState)
|
|
177
|
+
}
|
|
178
|
+
if (!label) return trimmedText
|
|
179
|
+
const isAsciiLabel = asciiLabelReg.test(label)
|
|
180
|
+
if (!trimmedText) {
|
|
181
|
+
return isAsciiLabel ? label + '.' : label
|
|
182
|
+
}
|
|
183
|
+
return label + (isAsciiLabel ? '. ' : ' ') + trimmedText
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const createAutoCaptionParagraph = (captionText, TokenConstructor) => {
|
|
187
|
+
const paragraphOpen = new TokenConstructor('paragraph_open', 'p', 1)
|
|
188
|
+
paragraphOpen.block = true
|
|
189
|
+
const inlineToken = new TokenConstructor('inline', '', 0)
|
|
190
|
+
inlineToken.block = true
|
|
191
|
+
inlineToken.content = captionText
|
|
192
|
+
const textToken = new TokenConstructor('text', '', 0)
|
|
193
|
+
textToken.content = captionText
|
|
194
|
+
inlineToken.children = [textToken]
|
|
195
|
+
const paragraphClose = new TokenConstructor('paragraph_close', 'p', -1)
|
|
196
|
+
paragraphClose.block = true
|
|
197
|
+
return [paragraphOpen, inlineToken, paragraphClose]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const getCaptionInlineToken = (tokens, range, caption) => {
|
|
201
|
+
if (caption.isPrev) {
|
|
202
|
+
const inlineIndex = range.start - 2
|
|
203
|
+
if (inlineIndex >= 0) return tokens[inlineIndex]
|
|
204
|
+
} else if (caption.isNext) {
|
|
205
|
+
return tokens[range.end + 2]
|
|
206
|
+
}
|
|
207
|
+
return null
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const getInlineLabelTextToken = (inlineToken, type, opt) => {
|
|
211
|
+
if (!inlineToken || !inlineToken.children) return null
|
|
212
|
+
const children = inlineToken.children
|
|
213
|
+
const classNames = opt.labelClassLookup[type] || opt.labelClassLookup.default
|
|
214
|
+
for (let i = 0; i < children.length; i++) {
|
|
215
|
+
const child = children[i]
|
|
216
|
+
if (!child || !child.attrs) continue
|
|
217
|
+
const classAttr = getTokenAttr(child, 'class')
|
|
218
|
+
if (!classAttr) continue
|
|
219
|
+
const classes = classAttr.split(/\s+/)
|
|
220
|
+
const matched = classNames.some(className => classes.includes(className))
|
|
221
|
+
if (!matched) continue
|
|
222
|
+
const textToken = children[i + 1]
|
|
223
|
+
if (textToken && textToken.type === 'text') {
|
|
224
|
+
return textToken
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return null
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const updateInlineTokenContent = (inlineToken, originalText, newText) => {
|
|
231
|
+
if (!inlineToken || typeof inlineToken.content !== 'string') return
|
|
232
|
+
if (!originalText) return
|
|
233
|
+
const index = inlineToken.content.indexOf(originalText)
|
|
234
|
+
if (index === -1) return
|
|
235
|
+
inlineToken.content =
|
|
236
|
+
inlineToken.content.slice(0, index) +
|
|
237
|
+
newText +
|
|
238
|
+
inlineToken.content.slice(index + originalText.length)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const ensureAutoFigureNumbering = (tokens, range, caption, figureNumberState, opt) => {
|
|
242
|
+
const captionType = caption.name === 'img' ? 'img' : (caption.name === 'table' ? 'table' : '')
|
|
243
|
+
if (!captionType) return
|
|
244
|
+
if (!shouldApplyLabelNumbering(captionType, opt)) return
|
|
245
|
+
const inlineToken = getCaptionInlineToken(tokens, range, caption)
|
|
246
|
+
if (!inlineToken) return
|
|
247
|
+
const labelTextToken = getInlineLabelTextToken(inlineToken, captionType, opt)
|
|
248
|
+
if (!labelTextToken || typeof labelTextToken.content !== 'string') return
|
|
249
|
+
const existingMatch = labelTextToken.content.match(trailingDigitsReg)
|
|
250
|
+
if (existingMatch && existingMatch[1]) {
|
|
251
|
+
const explicitValue = parseInt(existingMatch[1], 10)
|
|
252
|
+
if (!Number.isNaN(explicitValue) && explicitValue > (figureNumberState[captionType] || 0)) {
|
|
253
|
+
figureNumberState[captionType] = explicitValue
|
|
254
|
+
}
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
figureNumberState[captionType] = (figureNumberState[captionType] || 0) + 1
|
|
258
|
+
const baseLabel = labelTextToken.content.replace(/\s*\d+$/, '').trim() || labelTextToken.content.trim()
|
|
259
|
+
if (!baseLabel) return
|
|
260
|
+
const joint = asciiLabelReg.test(baseLabel) ? ' ' : ''
|
|
261
|
+
const newLabelText = baseLabel + joint + figureNumberState[captionType]
|
|
262
|
+
const originalText = labelTextToken.content
|
|
263
|
+
labelTextToken.content = newLabelText
|
|
264
|
+
updateInlineTokenContent(inlineToken, originalText, newLabelText)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const getAutoCaptionFromImage = (imageToken, opt, fallbackLabelState) => {
|
|
268
|
+
if (!opt.autoCaptionDetection) return ''
|
|
269
|
+
const tryMatch = (text) => {
|
|
270
|
+
if (!text) return ''
|
|
271
|
+
const trimmed = text.trim()
|
|
272
|
+
if (trimmed && imgCaptionMarkReg && trimmed.match(imgCaptionMarkReg)) {
|
|
273
|
+
return trimmed
|
|
274
|
+
}
|
|
275
|
+
return ''
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const altText = getImageAltText(imageToken)
|
|
279
|
+
let caption = tryMatch(altText)
|
|
280
|
+
if (caption) {
|
|
281
|
+
clearImageAltAttr(imageToken)
|
|
282
|
+
return caption
|
|
283
|
+
}
|
|
284
|
+
if (!caption && opt.autoAltCaption) {
|
|
285
|
+
const altForFallback = altText || ''
|
|
286
|
+
caption = buildCaptionWithFallback(altForFallback, opt.autoAltCaption, 'img', fallbackLabelState)
|
|
287
|
+
if (imageToken) {
|
|
288
|
+
clearImageAltAttr(imageToken)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (caption) return caption
|
|
292
|
+
|
|
293
|
+
const titleText = getImageTitleText(imageToken)
|
|
294
|
+
caption = tryMatch(titleText)
|
|
295
|
+
if (caption) {
|
|
296
|
+
clearImageTitleAttr(imageToken)
|
|
297
|
+
return caption
|
|
298
|
+
}
|
|
299
|
+
if (!caption && opt.autoTitleCaption) {
|
|
300
|
+
const titleForFallback = titleText || ''
|
|
301
|
+
caption = buildCaptionWithFallback(titleForFallback, opt.autoTitleCaption, 'img', fallbackLabelState)
|
|
302
|
+
if (imageToken) {
|
|
303
|
+
clearImageTitleAttr(imageToken)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return caption
|
|
307
|
+
}
|
|
7
308
|
|
|
8
309
|
const getHtmlReg = (tag) => {
|
|
9
|
-
if (htmlRegCache
|
|
10
|
-
|
|
310
|
+
if (htmlRegCache.has(tag)) return htmlRegCache.get(tag)
|
|
311
|
+
const regexStr = `^<${tag} ?[^>]*?>[\\s\\S]*?<\\/${tag}>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)`
|
|
11
312
|
const reg = new RegExp(regexStr)
|
|
12
|
-
htmlRegCache
|
|
313
|
+
htmlRegCache.set(tag, reg)
|
|
13
314
|
return reg
|
|
14
315
|
}
|
|
15
316
|
|
|
@@ -26,13 +327,13 @@ const getCaptionName = (token) => {
|
|
|
26
327
|
return ''
|
|
27
328
|
}
|
|
28
329
|
|
|
29
|
-
const checkPrevCaption = (
|
|
330
|
+
const checkPrevCaption = (tokens, n, caption, fNum, sp, opt, TokenConstructor) => {
|
|
30
331
|
if(n < 3) return caption
|
|
31
|
-
const captionStartToken =
|
|
32
|
-
const captionEndToken =
|
|
332
|
+
const captionStartToken = tokens[n-3]
|
|
333
|
+
const captionEndToken = tokens[n-1]
|
|
33
334
|
if (captionStartToken === undefined || captionEndToken === undefined) return
|
|
34
|
-
if (captionStartToken.type !== 'paragraph_open'
|
|
35
|
-
setCaptionParagraph(n-3,
|
|
335
|
+
if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
|
|
336
|
+
setCaptionParagraph(n-3, { tokens, Token: TokenConstructor }, caption, fNum, sp, opt)
|
|
36
337
|
const captionName = getCaptionName(captionStartToken)
|
|
37
338
|
if(!captionName) return
|
|
38
339
|
caption.name = captionName
|
|
@@ -40,13 +341,13 @@ const checkPrevCaption = (state, n, caption, fNum, sp, opt) => {
|
|
|
40
341
|
return
|
|
41
342
|
}
|
|
42
343
|
|
|
43
|
-
const checkNextCaption = (
|
|
44
|
-
if (en + 2 >
|
|
45
|
-
const captionStartToken =
|
|
46
|
-
const captionEndToken =
|
|
344
|
+
const checkNextCaption = (tokens, en, caption, fNum, sp, opt, TokenConstructor) => {
|
|
345
|
+
if (en + 2 > tokens.length) return
|
|
346
|
+
const captionStartToken = tokens[en+1]
|
|
347
|
+
const captionEndToken = tokens[en+3]
|
|
47
348
|
if (captionStartToken === undefined || captionEndToken === undefined) return
|
|
48
|
-
if (captionStartToken.type !== 'paragraph_open'
|
|
49
|
-
setCaptionParagraph(en+1,
|
|
349
|
+
if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
|
|
350
|
+
setCaptionParagraph(en+1, { tokens, Token: TokenConstructor }, caption, fNum, sp, opt)
|
|
50
351
|
const captionName = getCaptionName(captionStartToken)
|
|
51
352
|
if(!captionName) return
|
|
52
353
|
caption.name = captionName
|
|
@@ -55,8 +356,12 @@ const checkNextCaption = (state, en, caption, fNum, sp, opt) => {
|
|
|
55
356
|
}
|
|
56
357
|
|
|
57
358
|
const cleanCaptionTokenAttrs = (token, captionName) => {
|
|
58
|
-
const reg = new RegExp(' *?f-' + captionName)
|
|
59
359
|
if (!token.attrs) return
|
|
360
|
+
let reg = cleanCaptionRegCache.get(captionName)
|
|
361
|
+
if (!reg) {
|
|
362
|
+
reg = new RegExp(' *?f-' + captionName)
|
|
363
|
+
cleanCaptionRegCache.set(captionName, reg)
|
|
364
|
+
}
|
|
60
365
|
for (let i = token.attrs.length - 1; i >= 0; i--) {
|
|
61
366
|
if (token.attrs[i][0] === 'class') {
|
|
62
367
|
token.attrs[i][1] = token.attrs[i][1].replace(reg, '').trim()
|
|
@@ -65,86 +370,81 @@ const cleanCaptionTokenAttrs = (token, captionName) => {
|
|
|
65
370
|
}
|
|
66
371
|
}
|
|
67
372
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
isNoCaption = true
|
|
81
|
-
break
|
|
82
|
-
}
|
|
373
|
+
const resolveFigureClassName = (checkTokenTagName, caption, sp, opt) => {
|
|
374
|
+
let className = 'f-' + checkTokenTagName
|
|
375
|
+
if (opt.allIframeTypeFigureClassName === '') {
|
|
376
|
+
if (sp.isVideoIframe) {
|
|
377
|
+
className = 'f-video'
|
|
378
|
+
}
|
|
379
|
+
if (sp.isIframeTypeBlockquote) {
|
|
380
|
+
let figureClassThatWrapsIframeTypeBlockquote = opt.figureClassThatWrapsIframeTypeBlockquote
|
|
381
|
+
if ((caption.isPrev || caption.isNext) &&
|
|
382
|
+
figureClassThatWrapsIframeTypeBlockquote === 'f-img' &&
|
|
383
|
+
(caption.name === 'blockquote' || caption.name === 'img')) {
|
|
384
|
+
figureClassThatWrapsIframeTypeBlockquote = 'f-img'
|
|
83
385
|
}
|
|
386
|
+
className = figureClassThatWrapsIframeTypeBlockquote
|
|
84
387
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
388
|
+
} else {
|
|
389
|
+
if (checkTokenTagName === 'iframe' || sp.isIframeTypeBlockquote) {
|
|
390
|
+
className = opt.allIframeTypeFigureClassName
|
|
88
391
|
}
|
|
89
392
|
}
|
|
393
|
+
return className
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const applyCaptionDrivenFigureClass = (caption, sp, opt) => {
|
|
397
|
+
if (!sp) return
|
|
398
|
+
const figureClassForSlides = opt.figureClassThatWrapsSlides
|
|
399
|
+
if (!figureClassForSlides) return
|
|
400
|
+
const detectedMark = (sp.captionDecision && sp.captionDecision.mark) || (caption && caption.name) || ''
|
|
401
|
+
if (detectedMark !== 'slide') return
|
|
402
|
+
if (opt.allIframeTypeFigureClassName && sp.figureClassName === opt.allIframeTypeFigureClassName) return
|
|
403
|
+
sp.figureClassName = figureClassForSlides
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
const changePrevCaptionPosition = (tokens, n, caption, opt) => {
|
|
408
|
+
const captionStartToken = tokens[n-3]
|
|
409
|
+
const captionInlineToken = tokens[n-2]
|
|
410
|
+
const captionEndToken = tokens[n-1]
|
|
90
411
|
|
|
91
412
|
cleanCaptionTokenAttrs(captionStartToken, caption.name)
|
|
92
413
|
captionStartToken.type = 'figcaption_open'
|
|
93
414
|
captionStartToken.tag = 'figcaption'
|
|
94
415
|
captionEndToken.type = 'figcaption_close'
|
|
95
416
|
captionEndToken.tag = 'figcaption'
|
|
96
|
-
|
|
97
|
-
|
|
417
|
+
tokens.splice(n + 2, 0, captionStartToken, captionInlineToken, captionEndToken)
|
|
418
|
+
tokens.splice(n-3, 3)
|
|
98
419
|
return true
|
|
99
420
|
}
|
|
100
421
|
|
|
101
|
-
const changeNextCaptionPosition = (
|
|
102
|
-
const captionStartToken =
|
|
103
|
-
const captionInlineToken =
|
|
104
|
-
const captionEndToken =
|
|
422
|
+
const changeNextCaptionPosition = (tokens, en, caption, opt) => {
|
|
423
|
+
const captionStartToken = tokens[en+2] // +1: text node for figure.
|
|
424
|
+
const captionInlineToken = tokens[en+3]
|
|
425
|
+
const captionEndToken = tokens[en+4]
|
|
105
426
|
cleanCaptionTokenAttrs(captionStartToken, caption.name)
|
|
106
427
|
captionStartToken.type = 'figcaption_open'
|
|
107
428
|
captionStartToken.tag = 'figcaption'
|
|
108
429
|
captionEndToken.type = 'figcaption_close'
|
|
109
430
|
captionEndToken.tag = 'figcaption'
|
|
110
|
-
|
|
111
|
-
|
|
431
|
+
tokens.splice(en, 0, captionStartToken, captionInlineToken, captionEndToken)
|
|
432
|
+
tokens.splice(en+5, 3)
|
|
112
433
|
return true
|
|
113
434
|
}
|
|
114
435
|
|
|
115
|
-
const wrapWithFigure = (
|
|
436
|
+
const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInsteadOfWrap, sp, opt, TokenConstructor) => {
|
|
116
437
|
let n = range.start
|
|
117
438
|
let en = range.end
|
|
118
|
-
const figureStartToken = new
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (opt.allIframeTypeFigureClassName === '') {
|
|
122
|
-
if (sp.isVideoIframe) {
|
|
123
|
-
figureStartToken.attrSet('class', 'f-video')
|
|
124
|
-
}
|
|
125
|
-
if (sp.isIframeTypeBlockquote) {
|
|
126
|
-
let figureClassThatWrapsIframeTypeBlockquote = 'i-frame'
|
|
127
|
-
if (caption.isPrev || caption.isNext) {
|
|
128
|
-
if (caption.name === 'blockquote' || caption.name === 'img') {
|
|
129
|
-
figureClassThatWrapsIframeTypeBlockquote = 'f-img'
|
|
130
|
-
}
|
|
131
|
-
figureStartToken.attrSet('class', figureClassThatWrapsIframeTypeBlockquote)
|
|
132
|
-
} else {
|
|
133
|
-
figureClassThatWrapsIframeTypeBlockquote = opt.figureClassThatWrapsIframeTypeBlockquote
|
|
134
|
-
figureStartToken.attrSet('class', figureClassThatWrapsIframeTypeBlockquote)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
} else {
|
|
138
|
-
if (checkTokenTagName === 'iframe' || sp.isIframeTypeBlockquote) {
|
|
139
|
-
figureStartToken.attrSet('class', opt.allIframeTypeFigureClassName)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
439
|
+
const figureStartToken = new TokenConstructor('figure_open', 'figure', 1)
|
|
440
|
+
const figureClassName = sp.figureClassName || resolveFigureClassName(checkTokenTagName, caption, sp, opt)
|
|
441
|
+
figureStartToken.attrSet('class', figureClassName)
|
|
142
442
|
|
|
143
443
|
if(/pre-(?:code|samp)/.test(checkTokenTagName) && opt.roleDocExample) {
|
|
144
444
|
figureStartToken.attrSet('role', 'doc-example')
|
|
145
445
|
}
|
|
146
|
-
const figureEndToken = new
|
|
147
|
-
const breakToken = new
|
|
446
|
+
const figureEndToken = new TokenConstructor('figure_close', 'figure', -1)
|
|
447
|
+
const breakToken = new TokenConstructor('text', '', 0)
|
|
148
448
|
breakToken.content = '\n'
|
|
149
449
|
if (opt.styleProcess && caption.isNext && sp.attrs.length > 0) {
|
|
150
450
|
for (let attr of sp.attrs) {
|
|
@@ -152,18 +452,18 @@ const wrapWithFigure = (state, range, checkTokenTagName, caption, replaceInstead
|
|
|
152
452
|
}
|
|
153
453
|
}
|
|
154
454
|
// For vsce
|
|
155
|
-
if(
|
|
156
|
-
for (let attr of
|
|
455
|
+
if(tokens[n].attrs && caption.name === 'img') {
|
|
456
|
+
for (let attr of tokens[n].attrs) {
|
|
157
457
|
figureStartToken.attrJoin(attr[0], attr[1])
|
|
158
458
|
}
|
|
159
459
|
}
|
|
160
460
|
if (replaceInsteadOfWrap) {
|
|
161
|
-
|
|
162
|
-
|
|
461
|
+
tokens.splice(en, 1, breakToken, figureEndToken, breakToken)
|
|
462
|
+
tokens.splice(n, 1, figureStartToken, breakToken)
|
|
163
463
|
en = en + 2
|
|
164
464
|
} else {
|
|
165
|
-
|
|
166
|
-
|
|
465
|
+
tokens.splice(en+1, 0, figureEndToken, breakToken)
|
|
466
|
+
tokens.splice(n, 0, figureStartToken, breakToken)
|
|
167
467
|
en = en + 3
|
|
168
468
|
}
|
|
169
469
|
range.start = n
|
|
@@ -171,343 +471,493 @@ const wrapWithFigure = (state, range, checkTokenTagName, caption, replaceInstead
|
|
|
171
471
|
return
|
|
172
472
|
}
|
|
173
473
|
|
|
174
|
-
const checkCaption = (
|
|
175
|
-
checkPrevCaption(
|
|
474
|
+
const checkCaption = (tokens, n, en, caption, fNum, sp, opt, TokenConstructor) => {
|
|
475
|
+
checkPrevCaption(tokens, n, caption, fNum, sp, opt, TokenConstructor)
|
|
176
476
|
if (caption.isPrev) return
|
|
177
|
-
checkNextCaption(
|
|
477
|
+
checkNextCaption(tokens, en, caption, fNum, sp, opt, TokenConstructor)
|
|
178
478
|
return
|
|
179
479
|
}
|
|
180
480
|
|
|
181
|
-
const
|
|
182
|
-
const tokens = state.tokens
|
|
183
|
-
const checkTypes = ['table', 'pre', 'blockquote']
|
|
184
|
-
const htmlTags = ['video', 'audio', 'iframe', 'blockquote', 'div']
|
|
481
|
+
const nestedContainers = ['blockquote', 'list_item', 'dd']
|
|
185
482
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
483
|
+
const getNestedContainerType = (token) => {
|
|
484
|
+
if (!token || token.type.indexOf('_open') === -1) return null
|
|
485
|
+
const typeName = token.type.replace('_open', '')
|
|
486
|
+
if (nestedContainers.includes(typeName)) {
|
|
487
|
+
return typeName
|
|
190
488
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
489
|
+
return null
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const resetRangeState = (range, start) => {
|
|
493
|
+
range.start = start
|
|
494
|
+
range.end = start
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const resetCaptionState = (caption) => {
|
|
498
|
+
caption.mark = ''
|
|
499
|
+
caption.name = ''
|
|
500
|
+
caption.nameSuffix = ''
|
|
501
|
+
caption.isPrev = false
|
|
502
|
+
caption.isNext = false
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const resetSpecialState = (sp) => {
|
|
506
|
+
sp.attrs.length = 0
|
|
507
|
+
sp.isVideoIframe = false
|
|
508
|
+
sp.isIframeTypeBlockquote = false
|
|
509
|
+
sp.hasImgCaption = false
|
|
510
|
+
sp.figureClassName = ''
|
|
511
|
+
sp.captionDecision = null
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const findClosingTokenIndex = (tokens, startIndex, tag) => {
|
|
515
|
+
let depth = 1
|
|
516
|
+
let i = startIndex + 1
|
|
517
|
+
while (i < tokens.length) {
|
|
518
|
+
const token = tokens[i]
|
|
519
|
+
if (token.type === `${tag}_open`) depth++
|
|
520
|
+
if (token.type === `${tag}_close`) {
|
|
521
|
+
depth--
|
|
522
|
+
if (depth === 0) return i
|
|
523
|
+
}
|
|
524
|
+
i++
|
|
525
|
+
}
|
|
526
|
+
return startIndex
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const detectCheckTypeOpen = (tokens, token, n, caption) => {
|
|
530
|
+
if (!token) return null
|
|
531
|
+
const baseType = CHECK_TYPE_TOKEN_MAP[token.type]
|
|
532
|
+
if (!baseType) return null
|
|
533
|
+
if (n > 1 && tokens[n - 2] && tokens[n - 2].type === 'figure_open') return null
|
|
534
|
+
let tagName = token.tag
|
|
535
|
+
caption.name = baseType
|
|
536
|
+
if (baseType === 'pre') {
|
|
537
|
+
if (tokens[n + 1] && tokens[n + 1].tag === 'code') caption.mark = 'pre-code'
|
|
538
|
+
if (tokens[n + 1] && tokens[n + 1].tag === 'samp') caption.mark = 'pre-samp'
|
|
539
|
+
caption.name = caption.mark
|
|
540
|
+
}
|
|
541
|
+
const en = findClosingTokenIndex(tokens, n, tagName)
|
|
542
|
+
return {
|
|
543
|
+
type: 'block',
|
|
544
|
+
tagName,
|
|
545
|
+
en,
|
|
546
|
+
replaceInsteadOfWrap: false,
|
|
547
|
+
wrapWithoutCaption: false,
|
|
548
|
+
canWrap: true,
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const detectFenceToken = (token, n, caption) => {
|
|
553
|
+
if (!token || token.type !== 'fence' || token.tag !== 'code' || !token.block) return null
|
|
554
|
+
let tagName = 'pre-code'
|
|
555
|
+
if (sampLangReg.test(token.info)) {
|
|
556
|
+
token.tag = 'samp'
|
|
557
|
+
tagName = 'pre-samp'
|
|
558
|
+
}
|
|
559
|
+
caption.name = tagName
|
|
560
|
+
return {
|
|
561
|
+
type: 'fence',
|
|
562
|
+
tagName,
|
|
563
|
+
en: n,
|
|
564
|
+
replaceInsteadOfWrap: false,
|
|
565
|
+
wrapWithoutCaption: false,
|
|
566
|
+
canWrap: true,
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const detectHtmlBlockToken = (tokens, token, n, caption, sp, opt) => {
|
|
571
|
+
if (!token || token.type !== 'html_block') return null
|
|
572
|
+
const blueskyContMatch = token.content.match(blueskyEmbedReg)
|
|
573
|
+
let matchedTag = ''
|
|
574
|
+
for (let i = 0; i < HTML_TAG_CANDIDATES.length; i++) {
|
|
575
|
+
const candidate = HTML_TAG_CANDIDATES[i]
|
|
576
|
+
const treatDivAsIframe = candidate === 'div'
|
|
577
|
+
const lookupTag = treatDivAsIframe ? 'div' : candidate
|
|
578
|
+
const hasTag = token.content.match(getHtmlReg(lookupTag))
|
|
579
|
+
const isBlueskyBlockquote = !hasTag && blueskyContMatch && candidate === 'blockquote'
|
|
580
|
+
if (!(hasTag || isBlueskyBlockquote)) continue
|
|
581
|
+
if (hasTag) {
|
|
582
|
+
if ((hasTag[2] && hasTag[3] !== '\n') || (hasTag[1] !== '\n' && hasTag[2] === undefined)) {
|
|
583
|
+
token.content += '\n'
|
|
584
|
+
}
|
|
585
|
+
matchedTag = treatDivAsIframe ? 'iframe' : candidate
|
|
586
|
+
if (treatDivAsIframe) {
|
|
587
|
+
sp.isVideoIframe = true
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
let addedCont = ''
|
|
591
|
+
const tokensLength = tokens.length
|
|
592
|
+
let j = n + 1
|
|
593
|
+
while (j < tokensLength) {
|
|
594
|
+
const nextToken = tokens[j]
|
|
595
|
+
if (nextToken.type === 'inline' && endBlockquoteScriptReg.test(nextToken.content)) {
|
|
596
|
+
addedCont += nextToken.content + '\n'
|
|
597
|
+
if (tokens[j + 1] && tokens[j + 1].type === 'paragraph_close') {
|
|
598
|
+
tokens.splice(j + 1, 1)
|
|
233
599
|
}
|
|
234
|
-
|
|
600
|
+
nextToken.content = ''
|
|
601
|
+
nextToken.children.forEach((child) => {
|
|
602
|
+
child.content = ''
|
|
603
|
+
})
|
|
604
|
+
break
|
|
235
605
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
606
|
+
if (nextToken.type === 'paragraph_open') {
|
|
607
|
+
addedCont += '\n'
|
|
608
|
+
tokens.splice(j, 1)
|
|
609
|
+
continue
|
|
240
610
|
}
|
|
241
|
-
|
|
611
|
+
j++
|
|
242
612
|
}
|
|
613
|
+
token.content += addedCont
|
|
614
|
+
matchedTag = 'blockquote'
|
|
615
|
+
}
|
|
616
|
+
break
|
|
617
|
+
}
|
|
618
|
+
if (!matchedTag) return null
|
|
619
|
+
if (matchedTag === 'blockquote') {
|
|
620
|
+
const isIframeTypeBlockquote = token.content.match(classNameReg)
|
|
621
|
+
if (isIframeTypeBlockquote) {
|
|
622
|
+
sp.isIframeTypeBlockquote = true
|
|
623
|
+
} else {
|
|
624
|
+
return null
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (matchedTag === 'iframe' && videoIframeReg.test(token.content)) {
|
|
628
|
+
sp.isVideoIframe = true
|
|
629
|
+
}
|
|
630
|
+
caption.name = matchedTag
|
|
631
|
+
let wrapWithoutCaption = false
|
|
632
|
+
if (matchedTag === 'iframe' && opt.iframeWithoutCaption) {
|
|
633
|
+
wrapWithoutCaption = true
|
|
634
|
+
} else if (matchedTag === 'video' && opt.videoWithoutCaption) {
|
|
635
|
+
wrapWithoutCaption = true
|
|
636
|
+
} else if (matchedTag === 'audio' && opt.audioWithoutCaption) {
|
|
637
|
+
wrapWithoutCaption = true
|
|
638
|
+
} else if (matchedTag === 'blockquote' && sp.isIframeTypeBlockquote && opt.iframeTypeBlockquoteWithoutCaption) {
|
|
639
|
+
wrapWithoutCaption = true
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
type: 'html',
|
|
643
|
+
tagName: matchedTag,
|
|
644
|
+
en: n,
|
|
645
|
+
replaceInsteadOfWrap: false,
|
|
646
|
+
wrapWithoutCaption,
|
|
647
|
+
canWrap: true,
|
|
648
|
+
}
|
|
649
|
+
}
|
|
243
650
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
651
|
+
const detectImageParagraph = (tokens, token, nextToken, n, caption, sp, opt) => {
|
|
652
|
+
if (!token || token.type !== 'paragraph_open') return null
|
|
653
|
+
if (!nextToken || nextToken.type !== 'inline' || !nextToken.children || nextToken.children.length === 0) return null
|
|
654
|
+
if (nextToken.children[0].type !== 'image') return null
|
|
655
|
+
|
|
656
|
+
let imageNum = 1
|
|
657
|
+
let isMultipleImagesHorizontal = true
|
|
658
|
+
let isMultipleImagesVertical = true
|
|
659
|
+
let isValid = true
|
|
660
|
+
caption.name = 'img'
|
|
661
|
+
const children = nextToken.children
|
|
662
|
+
const childrenLength = children.length
|
|
663
|
+
for (let childIndex = 1; childIndex < childrenLength; childIndex++) {
|
|
664
|
+
const child = children[childIndex]
|
|
665
|
+
if (childIndex === childrenLength - 1 && child.type === 'text') {
|
|
666
|
+
let imageAttrs = child.content && child.content.match(imageAttrsReg)
|
|
667
|
+
if (imageAttrs) {
|
|
668
|
+
imageAttrs = imageAttrs[1].split(/ +/)
|
|
669
|
+
for (let i = 0; i < imageAttrs.length; i++) {
|
|
670
|
+
if (classAttrReg.test(imageAttrs[i])) {
|
|
671
|
+
imageAttrs[i] = imageAttrs[i].replace(classAttrReg, 'class=')
|
|
258
672
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
wrapWithFigure(state, range, checkTokenTagName, caption, false, sp, opt)
|
|
262
|
-
break
|
|
673
|
+
if (idAttrReg.test(imageAttrs[i])) {
|
|
674
|
+
imageAttrs[i] = imageAttrs[i].replace(idAttrReg, 'id=')
|
|
263
675
|
}
|
|
676
|
+
const imageAttr = imageAttrs[i].match(attrParseReg)
|
|
677
|
+
if (!imageAttr || !imageAttr[1]) continue
|
|
678
|
+
sp.attrs.push([imageAttr[1], imageAttr[2]])
|
|
264
679
|
}
|
|
265
680
|
break
|
|
266
681
|
}
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (token.type === 'html_block') {
|
|
271
|
-
let ctj = 0
|
|
272
|
-
let hasTag
|
|
273
|
-
while (ctj < htmlTags.length) {
|
|
274
|
-
if (htmlTags[ctj] === 'div') {
|
|
275
|
-
// for vimeo
|
|
276
|
-
hasTag = token.content.match(getHtmlReg('div'))
|
|
277
|
-
htmlTags[ctj] = 'iframe'
|
|
278
|
-
sp.isVideoIframe = true
|
|
279
|
-
} else {
|
|
280
|
-
hasTag = token.content.match(getHtmlReg(htmlTags[ctj]))
|
|
281
|
-
}
|
|
282
|
-
const blueskyContMatch = token.content.match(blueskyEmbedReg)
|
|
283
|
-
if (!(hasTag || (blueskyContMatch && htmlTags[ctj] === 'blockquote'))) {
|
|
284
|
-
ctj++
|
|
285
|
-
continue
|
|
286
|
-
}
|
|
287
|
-
if (hasTag) {
|
|
288
|
-
if ((hasTag[2] && hasTag[3] !== '\n') || (hasTag[1] !== '\n' && hasTag[2] === undefined)) {
|
|
289
|
-
token.content += '\n'
|
|
290
|
-
}
|
|
291
|
-
} else if (blueskyContMatch) {
|
|
292
|
-
let addedCont = ''
|
|
293
|
-
const tokensChildren = tokens
|
|
294
|
-
const tokensLength = tokensChildren.length
|
|
295
|
-
let j = n + 1
|
|
296
|
-
let hasEndBlockquote = true
|
|
297
|
-
while (j < tokensLength) {
|
|
298
|
-
const nextToken = tokens[j]
|
|
299
|
-
if (nextToken.type === 'inline' && /<\/blockquote> *<script[^>]*?><\/script>$/.test(nextToken.content)) {
|
|
300
|
-
addedCont += nextToken.content + '\n'
|
|
301
|
-
if (tokens[j + 1] && tokens[j + 1].type === 'paragraph_close') {
|
|
302
|
-
tokens.splice(j + 1, 1)
|
|
303
|
-
}
|
|
304
|
-
nextToken.content = ''
|
|
305
|
-
nextToken.children.forEach((child) => {
|
|
306
|
-
child.content = ''
|
|
307
|
-
})
|
|
308
|
-
break
|
|
309
|
-
}
|
|
310
|
-
if (nextToken.type === 'paragraph_open') {
|
|
311
|
-
addedCont += '\n'
|
|
312
|
-
tokens.splice(j, 1)
|
|
313
|
-
continue
|
|
314
|
-
}
|
|
315
|
-
j++
|
|
316
|
-
}
|
|
317
|
-
token.content += addedCont
|
|
318
|
-
if (!hasEndBlockquote) {
|
|
319
|
-
ctj++
|
|
320
|
-
continue
|
|
321
|
-
}
|
|
322
|
-
}
|
|
682
|
+
}
|
|
323
683
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
684
|
+
if (!opt.multipleImages) {
|
|
685
|
+
isValid = false
|
|
686
|
+
break
|
|
687
|
+
}
|
|
688
|
+
if (child.type === 'image') {
|
|
689
|
+
imageNum++
|
|
690
|
+
continue
|
|
691
|
+
}
|
|
692
|
+
if (isOnlySpacesText(child)) {
|
|
693
|
+
isMultipleImagesVertical = false
|
|
694
|
+
continue
|
|
695
|
+
}
|
|
696
|
+
if (child.type === 'softbreak') {
|
|
697
|
+
isMultipleImagesHorizontal = false
|
|
698
|
+
continue
|
|
699
|
+
}
|
|
700
|
+
isValid = false
|
|
701
|
+
break
|
|
702
|
+
}
|
|
703
|
+
if (isValid && imageNum > 1 && opt.multipleImages) {
|
|
704
|
+
if (isMultipleImagesHorizontal) {
|
|
705
|
+
caption.nameSuffix = '-horizontal'
|
|
706
|
+
} else if (isMultipleImagesVertical) {
|
|
707
|
+
caption.nameSuffix = '-vertical'
|
|
708
|
+
} else {
|
|
709
|
+
caption.nameSuffix = '-multiple'
|
|
710
|
+
}
|
|
711
|
+
for (let i = 0; i < childrenLength; i++) {
|
|
712
|
+
const child = children[i]
|
|
713
|
+
if (isOnlySpacesText(child)) {
|
|
714
|
+
child.content = ''
|
|
344
715
|
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
nextToken.children[0].type = 'image'
|
|
719
|
+
const en = n + 2
|
|
720
|
+
let tagName = 'img'
|
|
721
|
+
if (caption.nameSuffix) tagName += caption.nameSuffix
|
|
722
|
+
return {
|
|
723
|
+
type: 'image',
|
|
724
|
+
tagName,
|
|
725
|
+
en,
|
|
726
|
+
replaceInsteadOfWrap: true,
|
|
727
|
+
wrapWithoutCaption: isValid && !!opt.oneImageWithoutCaption,
|
|
728
|
+
canWrap: isValid,
|
|
729
|
+
imageToken: children[0]
|
|
730
|
+
}
|
|
731
|
+
}
|
|
345
732
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
733
|
+
const figureWithCaption = (state, opt) => {
|
|
734
|
+
let fNum = {
|
|
735
|
+
img: 0,
|
|
736
|
+
table: 0,
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const figureNumberState = {
|
|
740
|
+
img: 0,
|
|
741
|
+
table: 0,
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const fallbackLabelState = {
|
|
745
|
+
img: null,
|
|
746
|
+
table: null,
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
figureWithCaptionCore(state.tokens, opt, fNum, figureNumberState, fallbackLabelState, state.Token, null, 0)
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const figureWithCaptionCore = (tokens, opt, fNum, figureNumberState, fallbackLabelState, TokenConstructor, parentType = null, startIndex = 0) => {
|
|
753
|
+
const rRange = { start: startIndex, end: startIndex }
|
|
754
|
+
const rCaption = {
|
|
755
|
+
mark: '', name: '', nameSuffix: '', isPrev: false, isNext: false
|
|
756
|
+
}
|
|
757
|
+
const rSp = {
|
|
758
|
+
attrs: [], isVideoIframe: false, isIframeTypeBlockquote: false, hasImgCaption: false, captionDecision: null
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
let n = startIndex
|
|
762
|
+
while (n < tokens.length) {
|
|
763
|
+
const token = tokens[n]
|
|
764
|
+
const containerType = getNestedContainerType(token)
|
|
765
|
+
|
|
766
|
+
if (containerType && containerType !== 'blockquote') {
|
|
767
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, fNum, figureNumberState, fallbackLabelState, TokenConstructor, containerType, n + 1)
|
|
768
|
+
n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
|
|
769
|
+
continue
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
if (parentType && token.type === `${parentType}_close`) {
|
|
774
|
+
return n
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const nextToken = tokens[n + 1]
|
|
778
|
+
resetRangeState(rRange, n)
|
|
779
|
+
resetCaptionState(rCaption)
|
|
780
|
+
resetSpecialState(rSp)
|
|
781
|
+
|
|
782
|
+
const detection =
|
|
783
|
+
detectCheckTypeOpen(tokens, token, n, rCaption) ||
|
|
784
|
+
detectFenceToken(token, n, rCaption) ||
|
|
785
|
+
detectHtmlBlockToken(tokens, token, n, rCaption, rSp, opt) ||
|
|
786
|
+
detectImageParagraph(tokens, token, nextToken, n, rCaption, rSp, opt)
|
|
787
|
+
|
|
788
|
+
if (!detection) {
|
|
789
|
+
if (containerType === 'blockquote') {
|
|
790
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, fNum, figureNumberState, fallbackLabelState, TokenConstructor, containerType, n + 1)
|
|
791
|
+
n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
|
|
792
|
+
} else {
|
|
793
|
+
n++
|
|
355
794
|
}
|
|
795
|
+
continue
|
|
356
796
|
}
|
|
357
797
|
|
|
358
|
-
|
|
359
|
-
let ntChildTokenIndex = 1
|
|
360
|
-
let imageNum = 1
|
|
361
|
-
let isMultipleImagesHorizontal = true
|
|
362
|
-
let isMultipleImagesVertical = true
|
|
363
|
-
checkToken = true
|
|
364
|
-
caption.name = 'img'
|
|
365
|
-
const children = nextToken.children
|
|
366
|
-
const childrenLength = children.length
|
|
367
|
-
while (ntChildTokenIndex < childrenLength) {
|
|
368
|
-
const ntChildToken = children[ntChildTokenIndex]
|
|
369
|
-
if (ntChildTokenIndex === childrenLength - 1) {
|
|
370
|
-
let imageAttrs = ntChildToken.content.match(/^ *\{(.*?)\} *$/)
|
|
371
|
-
if(ntChildToken.type === 'text' && imageAttrs) {
|
|
372
|
-
imageAttrs = imageAttrs[1].split(/ +/)
|
|
373
|
-
let iai = 0
|
|
374
|
-
const attrsLength = imageAttrs.length
|
|
375
|
-
while (iai < attrsLength) {
|
|
376
|
-
if (/^\./.test(imageAttrs[iai])) {
|
|
377
|
-
imageAttrs[iai] = imageAttrs[iai].replace(/^\./, "class=")
|
|
378
|
-
}
|
|
379
|
-
if (/^#/.test(imageAttrs[iai])) {
|
|
380
|
-
imageAttrs[iai] = imageAttrs[iai].replace(/^#/, "id=")
|
|
381
|
-
}
|
|
382
|
-
let imageAttr = imageAttrs[iai].match(/^(.*?)="?(.*)"?$/)
|
|
383
|
-
if (!imageAttr || !imageAttr[1]) {
|
|
384
|
-
iai++
|
|
385
|
-
continue
|
|
386
|
-
}
|
|
387
|
-
sp.attrs.push([imageAttr[1], imageAttr[2]])
|
|
388
|
-
iai++
|
|
389
|
-
}
|
|
390
|
-
break
|
|
391
|
-
}
|
|
392
|
-
}
|
|
798
|
+
rRange.end = detection.en
|
|
393
799
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
} else if (ntChildToken.type === 'softbreak') {
|
|
406
|
-
isMultipleImagesHorizontal = false
|
|
407
|
-
if (isMultipleImagesHorizontal) {
|
|
408
|
-
isMultipleImagesVertical = false
|
|
409
|
-
}
|
|
410
|
-
} else {
|
|
411
|
-
checkToken = false
|
|
412
|
-
break
|
|
413
|
-
}
|
|
414
|
-
ntChildTokenIndex++
|
|
800
|
+
rSp.figureClassName = resolveFigureClassName(detection.tagName, rCaption, rSp, opt)
|
|
801
|
+
checkCaption(tokens, rRange.start, rRange.end, rCaption, fNum, rSp, opt, TokenConstructor)
|
|
802
|
+
applyCaptionDrivenFigureClass(rCaption, rSp, opt)
|
|
803
|
+
|
|
804
|
+
let hasCaption = rCaption.isPrev || rCaption.isNext
|
|
805
|
+
let pendingAutoCaption = ''
|
|
806
|
+
if (!hasCaption && detection.type === 'image' && opt.autoCaptionDetection) {
|
|
807
|
+
pendingAutoCaption = getAutoCaptionFromImage(detection.imageToken, opt, fallbackLabelState)
|
|
808
|
+
if (pendingAutoCaption) {
|
|
809
|
+
hasCaption = true
|
|
415
810
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
ntChildTokenIndex = 0
|
|
425
|
-
while (ntChildTokenIndex < childrenLength) {
|
|
426
|
-
const ccToken = children[ntChildTokenIndex]
|
|
427
|
-
if (ccToken.type === 'text' && /^ *$/.test(ccToken.content)) {
|
|
428
|
-
ccToken.content = ''
|
|
429
|
-
}
|
|
430
|
-
ntChildTokenIndex++
|
|
431
|
-
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (detection.canWrap === false) {
|
|
814
|
+
let nextIndex = rRange.end + 1
|
|
815
|
+
if (containerType === 'blockquote') {
|
|
816
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, fNum, figureNumberState, fallbackLabelState, TokenConstructor, containerType, rRange.start + 1)
|
|
817
|
+
nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : rRange.end) + 1)
|
|
432
818
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
nextToken.children[0].type = 'image'
|
|
819
|
+
n = nextIndex
|
|
820
|
+
continue
|
|
821
|
+
}
|
|
437
822
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
823
|
+
let shouldWrap = false
|
|
824
|
+
if (detection.type === 'html') {
|
|
825
|
+
shouldWrap = detection.canWrap !== false && (hasCaption || detection.wrapWithoutCaption)
|
|
826
|
+
} else if (detection.type === 'image') {
|
|
827
|
+
shouldWrap = detection.canWrap !== false && (hasCaption || detection.wrapWithoutCaption)
|
|
828
|
+
if (parentType === 'list_item' || isInListItem(tokens, n)) {
|
|
829
|
+
const isInTightList = token.hidden === true
|
|
830
|
+
if (isInTightList) {
|
|
831
|
+
shouldWrap = false
|
|
832
|
+
} else if (!hasCaption && !opt.oneImageWithoutCaption) {
|
|
833
|
+
shouldWrap = false
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
shouldWrap = detection.canWrap !== false && hasCaption
|
|
838
|
+
}
|
|
441
839
|
|
|
442
|
-
|
|
443
|
-
|
|
840
|
+
if (shouldWrap) {
|
|
841
|
+
if (pendingAutoCaption) {
|
|
842
|
+
const captionTokens = createAutoCaptionParagraph(pendingAutoCaption, TokenConstructor)
|
|
843
|
+
tokens.splice(rRange.start, 0, ...captionTokens)
|
|
844
|
+
const insertedLength = captionTokens.length
|
|
845
|
+
rRange.start += insertedLength
|
|
846
|
+
rRange.end += insertedLength
|
|
847
|
+
n += insertedLength
|
|
848
|
+
checkCaption(tokens, rRange.start, rRange.end, rCaption, fNum, rSp, opt, TokenConstructor)
|
|
849
|
+
applyCaptionDrivenFigureClass(rCaption, rSp, opt)
|
|
444
850
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
851
|
+
ensureAutoFigureNumbering(tokens, rRange, rCaption, figureNumberState, opt)
|
|
852
|
+
wrapWithFigure(tokens, rRange, detection.tagName, rCaption, detection.replaceInsteadOfWrap, rSp, opt, TokenConstructor)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
let nextIndex
|
|
856
|
+
if (!rCaption.name) {
|
|
857
|
+
nextIndex = n + 1
|
|
858
|
+
} else {
|
|
859
|
+
const en = rRange.end
|
|
860
|
+
if (rCaption.isPrev) {
|
|
861
|
+
changePrevCaptionPosition(tokens, rRange.start, rCaption, opt)
|
|
862
|
+
nextIndex = en + 1
|
|
863
|
+
} else if (rCaption.isNext) {
|
|
864
|
+
changeNextCaptionPosition(tokens, en, rCaption)
|
|
865
|
+
nextIndex = en + 4
|
|
866
|
+
} else {
|
|
867
|
+
nextIndex = en + 1
|
|
448
868
|
}
|
|
449
869
|
}
|
|
450
870
|
|
|
451
|
-
if (
|
|
871
|
+
if (containerType === 'blockquote') {
|
|
872
|
+
const closeIndex = figureWithCaptionCore(tokens, opt, fNum, figureNumberState, fallbackLabelState, TokenConstructor, containerType, rRange.start + 1)
|
|
873
|
+
const fallbackIndex = rCaption.name ? rRange.end : n
|
|
874
|
+
nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : fallbackIndex) + 1)
|
|
875
|
+
}
|
|
452
876
|
|
|
453
|
-
n =
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
877
|
+
n = nextIndex
|
|
878
|
+
}
|
|
879
|
+
return tokens.length
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const isInListItem = (() => {
|
|
883
|
+
const cache = new WeakMap()
|
|
884
|
+
return (tokens, idx) => {
|
|
885
|
+
if (cache.has(tokens)) {
|
|
886
|
+
const cachedResult = cache.get(tokens)
|
|
887
|
+
if (cachedResult[idx] !== undefined) {
|
|
888
|
+
return cachedResult[idx]
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
cache.set(tokens, {})
|
|
459
892
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
893
|
+
|
|
894
|
+
const result = cache.get(tokens)
|
|
895
|
+
|
|
896
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
897
|
+
if (tokens[i].type === 'list_item_open') {
|
|
898
|
+
result[idx] = true
|
|
899
|
+
return true
|
|
900
|
+
}
|
|
901
|
+
if (tokens[i].type === 'list_item_close' || tokens[i].type === 'list_open') {
|
|
902
|
+
result[idx] = false
|
|
903
|
+
return false
|
|
904
|
+
}
|
|
464
905
|
}
|
|
465
|
-
|
|
906
|
+
|
|
907
|
+
result[idx] = false
|
|
908
|
+
return false
|
|
466
909
|
}
|
|
467
|
-
|
|
468
|
-
}
|
|
910
|
+
})()
|
|
469
911
|
|
|
470
912
|
const mditFigureWithPCaption = (md, option) => {
|
|
471
913
|
let opt = {
|
|
914
|
+
// --- figure-wrapper behavior ---
|
|
472
915
|
classPrefix: 'f',
|
|
473
916
|
figureClassThatWrapsIframeTypeBlockquote: 'f-img',
|
|
917
|
+
figureClassThatWrapsSlides: 'f-slide',
|
|
474
918
|
styleProcess : true,
|
|
919
|
+
oneImageWithoutCaption: false,
|
|
920
|
+
iframeWithoutCaption: false,
|
|
921
|
+
videoWithoutCaption: false,
|
|
922
|
+
audioWithoutCaption: false,
|
|
923
|
+
iframeTypeBlockquoteWithoutCaption: false,
|
|
924
|
+
multipleImages: true,
|
|
925
|
+
roleDocExample: false,
|
|
926
|
+
allIframeTypeFigureClassName: '', // e.g. 'f-embed' to force a single class for iframe-like embeds (recommended)
|
|
927
|
+
|
|
928
|
+
// --- automatic caption detection heuristics ---
|
|
929
|
+
// Applies only to the first image within an image-only paragraph (even when multipleImages is true).
|
|
930
|
+
// Priority: caption paragraphs (before/after) > alt text > title attribute; auto detection only runs when no paragraph caption exists.
|
|
931
|
+
autoCaptionDetection: true,
|
|
932
|
+
autoAltCaption: false, // allow alt text (when matching markReg.img or fallback) to build captions automatically
|
|
933
|
+
autoTitleCaption: false, // same as above but reads from the title attribute when alt isn't usable
|
|
934
|
+
|
|
935
|
+
// --- numbering controls ---
|
|
936
|
+
autoLabelNumber: false, // shorthand for enabling numbering for both img/table unless setLabelNumbers is provided explicitly
|
|
937
|
+
setLabelNumbers: [], // preferred; supports ['img'], ['table'], or both
|
|
938
|
+
|
|
939
|
+
// --- caption text formatting (delegated to p7d-markdown-it-p-captions) ---
|
|
475
940
|
hasNumClass: false,
|
|
476
|
-
scaleSuffix: false,
|
|
477
941
|
dquoteFilename: false,
|
|
478
942
|
strongFilename: false,
|
|
479
943
|
bLabel: false,
|
|
480
944
|
strongLabel: false,
|
|
481
945
|
jointSpaceUseHalfWidth: false,
|
|
482
|
-
oneImageWithoutCaption: false,
|
|
483
|
-
iframeWithoutCaption: false,
|
|
484
|
-
videoWithoutCaption: false,
|
|
485
|
-
iframeTypeBlockquoteWithoutCaption: false,
|
|
486
946
|
removeUnnumberedLabel: false,
|
|
487
947
|
removeUnnumberedLabelExceptMarks: [],
|
|
488
948
|
removeMarkNameInCaptionClass: false,
|
|
489
|
-
|
|
490
|
-
imgAltCaption: false,
|
|
491
|
-
setFigureNumber: false,
|
|
492
|
-
imgTitleCaption: false,
|
|
493
|
-
roleDocExample: false,
|
|
494
|
-
allIframeTypeFigureClassName: '',
|
|
949
|
+
wrapCaptionBody: false,
|
|
495
950
|
}
|
|
951
|
+
const hasExplicitSetLabelNumbers = option && Object.prototype.hasOwnProperty.call(option, 'setLabelNumbers')
|
|
496
952
|
if (option) Object.assign(opt, option)
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
opt.
|
|
501
|
-
|
|
502
|
-
for (let mark of opt.removeUnnumberedLabelExceptMarks) {
|
|
503
|
-
if (mark === 'img') opt.removeUnnumberedLabelExceptMarks.splice(opt.removeUnnumberedLabelExceptMarks.indexOf(mark), 1)
|
|
504
|
-
if (mark === 'table') opt.removeUnnumberedLabelExceptMarks.splice(opt.removeUnnumberedLabelExceptMarks.indexOf(mark), 1)
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
md.block.ruler.before('paragraph', 'img_attr_caption', (state) => {
|
|
508
|
-
imgAttrToPCaption(state, state.line, opt)
|
|
509
|
-
})
|
|
953
|
+
// Normalize option shorthands now so downstream logic works with a consistent { img, table } shape.
|
|
954
|
+
opt.setLabelNumbers = normalizeSetLabelNumbers(opt.setLabelNumbers)
|
|
955
|
+
if (opt.autoLabelNumber && !hasExplicitSetLabelNumbers) {
|
|
956
|
+
opt.setLabelNumbers.img = true
|
|
957
|
+
opt.setLabelNumbers.table = true
|
|
510
958
|
}
|
|
959
|
+
// Precompute `.f-*-label` permutations so numbering lookup doesn't rebuild arrays per caption.
|
|
960
|
+
opt.labelClassLookup = buildLabelClassLookup(opt)
|
|
511
961
|
|
|
512
962
|
//If nextCaption has `{}` style and `f-img-multipleImages`, when upgraded to markdown-it-attrs@4.2.0, the existing script will have `{}` style on nextCaption. Therefore, since markdown-it-attrs is md.core.ruler.before('linkify'), figure_with_caption will be processed after it.
|
|
513
963
|
md.core.ruler.before('replacements', 'figure_with_caption', (state) => {
|
|
@@ -515,4 +965,4 @@ const mditFigureWithPCaption = (md, option) => {
|
|
|
515
965
|
})
|
|
516
966
|
}
|
|
517
967
|
|
|
518
|
-
export default mditFigureWithPCaption
|
|
968
|
+
export default mditFigureWithPCaption
|