@peaceroad/markdown-it-figure-with-p-caption 0.11.0 → 0.13.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/index.js CHANGED
@@ -1,112 +1,136 @@
1
1
  import { setCaptionParagraph } from 'p7d-markdown-it-p-captions'
2
2
  import { imgAttrToPCaption, setAltToLabel, setTitleToLabel } from './imgAttrToPCaption.js'
3
3
 
4
- const checkPrevCaption = (state, n, caption, fNum, sp, opt) => {
5
- if(n < 3) return caption
6
- const captionStartToken = state.tokens[n-3]
7
- const captionEndToken = state.tokens[n-1]
8
- if (captionStartToken === undefined || captionEndToken === undefined) return
9
-
10
- if (captionStartToken.type !== 'paragraph_open' && captionEndToken.type !== 'paragraph_close') return
4
+ const htmlRegCache = new Map()
5
+ const classReg = /^f-(.+)$/
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 whitespaceReg = /^ *$/
14
+ const sampLangReg = /^ *(?:samp|shell|console)(?:(?= )|$)/
15
+ const endBlockquoteScriptReg = /<\/blockquote> *<script[^>]*?><\/script>$/
11
16
 
12
- setCaptionParagraph(n-3, state, caption, fNum, sp, opt)
17
+ const getHtmlReg = (tag) => {
18
+ if (htmlRegCache.has(tag)) return htmlRegCache.get(tag)
19
+ const regexStr = `^<${tag} ?[^>]*?>[\\s\\S]*?<\\/${tag}>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)`
20
+ const reg = new RegExp(regexStr)
21
+ htmlRegCache.set(tag, reg)
22
+ return reg
23
+ }
13
24
 
14
- let captionName = ''
15
- if (captionStartToken.attrs) {
16
- captionStartToken.attrs.forEach(attr => {
17
- let hasCaptionName = attr[1].match(/^f-(.+)$/)
18
- if (attr[0] === 'class' && hasCaptionName) captionName = hasCaptionName[1]
19
- })
25
+ const getCaptionName = (token) => {
26
+ if (!token.attrs) return ''
27
+ const attrs = token.attrs
28
+ for (let i = 0, len = attrs.length; i < len; i++) {
29
+ const attr = attrs[i]
30
+ if (attr[0] === 'class') {
31
+ const match = attr[1].match(classReg)
32
+ if (match) return match[1]
33
+ }
20
34
  }
35
+ return ''
36
+ }
37
+
38
+ const checkPrevCaption = (tokens, n, caption, fNum, sp, opt, TokenConstructor) => {
39
+ if(n < 3) return caption
40
+ const captionStartToken = tokens[n-3]
41
+ const captionEndToken = tokens[n-1]
42
+ if (captionStartToken === undefined || captionEndToken === undefined) return
43
+ if (captionStartToken.type !== 'paragraph_open' && captionEndToken.type !== 'paragraph_close') return
44
+ setCaptionParagraph(n-3, { tokens, Token: TokenConstructor }, caption, fNum, sp, opt)
45
+ const captionName = getCaptionName(captionStartToken)
21
46
  if(!captionName) return
22
47
  caption.name = captionName
23
48
  caption.isPrev = true
24
49
  return
25
50
  }
26
51
 
27
- const changePrevCaptionPosition = (state, n, caption, opt) => {
28
- const captionStartToken = state.tokens[n-3]
29
- const captionInlineToken = state.tokens[n-2]
30
- const captionEndToken = state.tokens[n-1]
52
+ const checkNextCaption = (tokens, en, caption, fNum, sp, opt, TokenConstructor) => {
53
+ if (en + 2 > tokens.length) return
54
+ const captionStartToken = tokens[en+1]
55
+ const captionEndToken = tokens[en+3]
56
+ if (captionStartToken === undefined || captionEndToken === undefined) return
57
+ if (captionStartToken.type !== 'paragraph_open' && captionEndToken.type !== 'paragraph_close') return
58
+ setCaptionParagraph(en+1, { tokens, Token: TokenConstructor }, caption, fNum, sp, opt)
59
+ const captionName = getCaptionName(captionStartToken)
60
+ if(!captionName) return
61
+ caption.name = captionName
62
+ caption.isNext = true
63
+ return
64
+ }
65
+
66
+ const cleanCaptionRegCache = new Map()
67
+
68
+ const cleanCaptionTokenAttrs = (token, captionName) => {
69
+ if (!token.attrs) return
70
+ let reg = cleanCaptionRegCache.get(captionName)
71
+ if (!reg) {
72
+ reg = new RegExp(' *?f-' + captionName)
73
+ cleanCaptionRegCache.set(captionName, reg)
74
+ }
75
+ for (let i = token.attrs.length - 1; i >= 0; i--) {
76
+ if (token.attrs[i][0] === 'class') {
77
+ token.attrs[i][1] = token.attrs[i][1].replace(reg, '').trim()
78
+ if (token.attrs[i][1] === '') token.attrs.splice(i, 1)
79
+ }
80
+ }
81
+ }
82
+
83
+ const changePrevCaptionPosition = (tokens, n, caption, opt) => {
84
+ const captionStartToken = tokens[n-3]
85
+ const captionInlineToken = tokens[n-2]
86
+ const captionEndToken = tokens[n-1]
31
87
 
32
88
  if (opt.imgAltCaption || opt.imgTitleCaption) {
33
89
  let isNoCaption = false
34
90
  if (captionInlineToken.attrs) {
35
- for (let attr of captionInlineToken.attrs) {
36
- if (attr[0] === 'class' && attr[1] === 'nocaption') isNoCaption = true
91
+ const attrs = captionInlineToken.attrs, len = attrs.length
92
+ for (let i = 0; i < len; i++) {
93
+ const attr = attrs[i]
94
+ if (attr[0] === 'class' && attr[1] === 'nocaption') {
95
+ isNoCaption = true
96
+ break
97
+ }
37
98
  }
38
99
  }
39
100
  if (isNoCaption) {
40
- state.tokens.splice(n-3, 3)
101
+ tokens.splice(n-3, 3)
41
102
  return false
42
103
  }
43
104
  }
44
105
 
45
- const attrReplaceReg = new RegExp(' *?f-' + caption.name)
46
- captionStartToken.attrs.forEach(attr => {
47
- if (attr[0] === 'class') {
48
- attr[1] = attr[1].replace(attrReplaceReg, '').trim()
49
- if(attr[1] === '') {
50
- captionStartToken.attrs.splice(captionStartToken.attrIndex('class'), 1)
51
- }
52
- }
53
- })
106
+ cleanCaptionTokenAttrs(captionStartToken, caption.name)
54
107
  captionStartToken.type = 'figcaption_open'
55
108
  captionStartToken.tag = 'figcaption'
56
109
  captionEndToken.type = 'figcaption_close'
57
110
  captionEndToken.tag = 'figcaption'
58
- state.tokens.splice(n + 2, 0, captionStartToken, captionInlineToken, captionEndToken)
59
- state.tokens.splice(n-3, 3)
111
+ tokens.splice(n + 2, 0, captionStartToken, captionInlineToken, captionEndToken)
112
+ tokens.splice(n-3, 3)
60
113
  return true
61
114
  }
62
115
 
63
- const checkNextCaption = (state, en, caption, fNum, sp, opt) => {
64
- if (en + 2 > state.tokens.length) return
65
- const captionStartToken = state.tokens[en+1]
66
- const captionEndToken = state.tokens[en+3]
67
- if (captionStartToken === undefined || captionEndToken === undefined) return
68
- if (captionStartToken.type !== 'paragraph_open' && captionEndToken.type !== 'paragraph_close') return
69
-
70
- setCaptionParagraph(en+1, state, caption, fNum, sp, opt)
71
-
72
- let captionName = ''
73
- if (captionStartToken.attrs) {
74
- captionStartToken.attrs.forEach(attr => {
75
- let hasCaptionName = attr[1].match(/^f-(.+)$/)
76
- if (attr[0] === 'class' && hasCaptionName) captionName = hasCaptionName[1]
77
- })
78
- }
79
- if(!captionName) return
80
- caption.name = captionName
81
- caption.isNext = true
82
- return
83
- }
84
-
85
- const changeNextCaptionPosition = (state, en, caption) => {
86
- const captionStartToken = state.tokens[en+2] // +1: text node for figure.
87
- const captionInlineToken = state.tokens[en+3]
88
- const captionEndToken = state.tokens[en+4]
89
- captionStartToken.attrs.forEach(attr => {
90
- if (attr[0] === 'class') {
91
- attr[1] = attr[1].replace(new RegExp(' *?f-' + caption.name), '').trim()
92
- if(attr[1] === '') {
93
- captionStartToken.attrs.splice(captionStartToken.attrIndex('class'), 1)
94
- }
95
- }
96
- })
116
+ const changeNextCaptionPosition = (tokens, en, caption) => {
117
+ const captionStartToken = tokens[en+2] // +1: text node for figure.
118
+ const captionInlineToken = tokens[en+3]
119
+ const captionEndToken = tokens[en+4]
120
+ cleanCaptionTokenAttrs(captionStartToken, caption.name)
97
121
  captionStartToken.type = 'figcaption_open'
98
122
  captionStartToken.tag = 'figcaption'
99
123
  captionEndToken.type = 'figcaption_close'
100
124
  captionEndToken.tag = 'figcaption'
101
- state.tokens.splice(en, 0, captionStartToken, captionInlineToken, captionEndToken)
102
- state.tokens.splice(en+5, 3)
125
+ tokens.splice(en, 0, captionStartToken, captionInlineToken, captionEndToken)
126
+ tokens.splice(en+5, 3)
103
127
  return true
104
128
  }
105
129
 
106
- const wrapWithFigure = (state, range, checkTokenTagName, caption, replaceInsteadOfWrap, sp, opt) => {
130
+ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInsteadOfWrap, sp, opt, TokenConstructor) => {
107
131
  let n = range.start
108
132
  let en = range.end
109
- const figureStartToken = new state.Token('figure_open', 'figure', 1)
133
+ const figureStartToken = new TokenConstructor('figure_open', 'figure', 1)
110
134
  figureStartToken.attrSet('class', 'f-' + checkTokenTagName)
111
135
 
112
136
  if (opt.allIframeTypeFigureClassName === '') {
@@ -134,8 +158,8 @@ const wrapWithFigure = (state, range, checkTokenTagName, caption, replaceInstead
134
158
  if(/pre-(?:code|samp)/.test(checkTokenTagName) && opt.roleDocExample) {
135
159
  figureStartToken.attrSet('role', 'doc-example')
136
160
  }
137
- const figureEndToken = new state.Token('figure_close', 'figure', -1)
138
- const breakToken = new state.Token('text', '', 0)
161
+ const figureEndToken = new TokenConstructor('figure_close', 'figure', -1)
162
+ const breakToken = new TokenConstructor('text', '', 0)
139
163
  breakToken.content = '\n'
140
164
  if (opt.styleProcess && caption.isNext && sp.attrs.length > 0) {
141
165
  for (let attr of sp.attrs) {
@@ -143,92 +167,146 @@ const wrapWithFigure = (state, range, checkTokenTagName, caption, replaceInstead
143
167
  }
144
168
  }
145
169
  // For vsce
146
- //console.log(caption)
147
- if(state.tokens[n].attrs && caption.name === 'img') {
148
- for (let attr of state.tokens[n].attrs) {
170
+ if(tokens[n].attrs && caption.name === 'img') {
171
+ for (let attr of tokens[n].attrs) {
149
172
  figureStartToken.attrJoin(attr[0], attr[1])
150
173
  }
151
174
  }
152
175
  if (replaceInsteadOfWrap) {
153
- state.tokens.splice(en, 1, breakToken, figureEndToken, breakToken)
154
- state.tokens.splice(n, 1, figureStartToken, breakToken)
176
+ tokens.splice(en, 1, breakToken, figureEndToken, breakToken)
177
+ tokens.splice(n, 1, figureStartToken, breakToken)
155
178
  en = en + 2
156
- //console.log(state.tokens[n].type, state.tokens[en].type)
157
179
  } else {
158
- state.tokens.splice(en+1, 0, figureEndToken, breakToken)
159
- state.tokens.splice(n, 0, figureStartToken, breakToken)
180
+ tokens.splice(en+1, 0, figureEndToken, breakToken)
181
+ tokens.splice(n, 0, figureStartToken, breakToken)
160
182
  en = en + 3
161
- //console.log(state.tokens[n].type, state.tokens[en].type)
162
183
  }
163
184
  range.start = n
164
185
  range.end = en
165
186
  return
166
187
  }
167
188
 
168
- const checkCaption = (state, n, en, caption, fNum, sp, opt) => {
169
- checkPrevCaption(state, n, caption, fNum, sp, opt)
189
+ const checkCaption = (tokens, n, en, caption, fNum, sp, opt, TokenConstructor) => {
190
+ checkPrevCaption(tokens, n, caption, fNum, sp, opt, TokenConstructor)
170
191
  if (caption.isPrev) return
171
- checkNextCaption(state, en, caption, fNum, sp, opt)
192
+ checkNextCaption(tokens, en, caption, fNum, sp, opt, TokenConstructor)
172
193
  return
173
194
  }
174
195
 
196
+ const processTokensRecursively = (tokens, opt, fNum, TokenConstructor, parentType) => {
197
+ const nestedContainers = ['blockquote', 'list_item', 'dd']
198
+
199
+ figureWithCaptionCore(tokens, opt, fNum, TokenConstructor, parentType)
200
+
201
+ const nestedRanges = []
202
+ let i = 0
203
+ while (i < tokens.length) {
204
+ const token = tokens[i]
205
+ let containerType = null
206
+ for (const container of nestedContainers) {
207
+ if (token.type === `${container}_open`) {
208
+ containerType = container
209
+ break
210
+ }
211
+ }
212
+ if (containerType) {
213
+ let depth = 1
214
+ let endIndex = i + 1
215
+ while (endIndex < tokens.length && depth > 0) {
216
+ if (tokens[endIndex].type === `${containerType}_open`) depth++
217
+ if (tokens[endIndex].type === `${containerType}_close`) depth--
218
+ endIndex++
219
+ }
220
+ if (depth === 0 && endIndex - i > 2) {
221
+ nestedRanges.push({
222
+ start: i + 1,
223
+ end: endIndex - 1,
224
+ type: containerType
225
+ })
226
+ }
227
+ i = endIndex
228
+ } else {
229
+ i++
230
+ }
231
+ }
232
+
233
+ for (let j = nestedRanges.length - 1; j >= 0; j--) {
234
+ const range = nestedRanges[j]
235
+ const innerTokens = tokens.slice(range.start, range.end)
236
+ if (innerTokens.length > 0) {
237
+ processTokensRecursively(innerTokens, opt, fNum, TokenConstructor, range.type)
238
+ tokens.splice(range.start, range.end - range.start, ...innerTokens)
239
+ }
240
+ }
241
+ }
242
+
175
243
  const figureWithCaption = (state, opt) => {
176
- let n = 0
177
244
  let fNum = {
178
245
  img: 0,
179
246
  table: 0,
180
247
  }
181
- while (n < state.tokens.length) {
182
- const token = state.tokens[n]
183
- const nextToken = state.tokens[n+1]
248
+
249
+ processTokensRecursively(state.tokens, opt, fNum, state.Token, null)
250
+ }
251
+
252
+ const figureWithCaptionCore = (tokens, opt, fNum, TokenConstructor, parentType) => {
253
+ const checkTypes = ['table', 'pre', 'blockquote']
254
+ const htmlTags = ['video', 'audio', 'iframe', 'blockquote', 'div']
255
+
256
+ const rRange = { start: 0, end: 0 }
257
+ const rCaption = {
258
+ mark: '', name: '', nameSuffix: '', isPrev: false, isNext: false
259
+ }
260
+ const rSp = {
261
+ attrs: [], isVideoIframe: false, isIframeTypeBlockquote: false, hasImgCaption: false
262
+ }
263
+
264
+ let n = 0
265
+ while (n < tokens.length) {
266
+ const token = tokens[n]
267
+ const nextToken = tokens[n+1]
184
268
  let en = n
185
- let range = {
186
- start: n,
187
- end: en,
188
- }
269
+
270
+ rRange.start = n
271
+ rRange.end = en
189
272
  let checkToken = false
190
273
  let checkTokenTagName = ''
191
- let caption = {
192
- mark: '',
193
- name: '',
194
- nameSuffix: '',
195
- isPrev: false,
196
- isNext: false,
197
- };
198
- const sp = {
199
- attrs: [],
200
- isVideoIframe: false,
201
- isIframeTypeBlockquote: false,
202
- hasImgCaption: false,
203
- }
274
+ rCaption.mark = ''
275
+ rCaption.name = ''
276
+ rCaption.nameSuffix = ''
277
+ rCaption.isPrev = false
278
+ rCaption.isNext = false
279
+
280
+ rSp.attrs.length = 0
281
+ rSp.isVideoIframe = false
282
+ rSp.isIframeTypeBlockquote = false
283
+ rSp.hasImgCaption = false
204
284
 
205
- const checkTypes = ['table', 'pre', 'blockquote']
206
285
  let cti = 0
207
- //console.log(state.tokens[n].type, state.tokens[n].tag)
208
286
  while (cti < checkTypes.length) {
209
287
  if (token.type === checkTypes[cti] + '_open') {
210
288
  // for n-1 token is line-break
211
- if (n > 1 && state.tokens[n-2].type === 'figure_open') {
289
+ if (n > 1 && tokens[n-2].type === 'figure_open') {
212
290
  cti++; continue
213
291
  }
214
292
  checkToken = true
215
293
  checkTokenTagName = token.tag
216
- caption.name = checkTypes[cti]
294
+ rCaption.name = checkTypes[cti]
217
295
  if (checkTypes[cti] === 'pre') {
218
- if (state.tokens[n+1].tag === 'code') caption.mark = 'pre-code'
219
- if (state.tokens[n+1].tag === 'samp') caption.mark = 'pre-samp'
220
- caption.name = caption.mark
296
+ if (tokens[n+1].tag === 'code') rCaption.mark = 'pre-code'
297
+ if (tokens[n+1].tag === 'samp') rCaption.mark = 'pre-samp'
298
+ rCaption.name = rCaption.mark
221
299
  }
222
- while (en < state.tokens.length) {
223
- if(state.tokens[en].type === checkTokenTagName + '_close') {
300
+ while (en < tokens.length) {
301
+ if(tokens[en].type === checkTokenTagName + '_close') {
224
302
  break
225
303
  }
226
304
  en++
227
305
  }
228
- range.end = en
229
- checkCaption(state, n, en, caption, fNum, sp, opt)
230
- if (caption.isPrev || caption.isNext) {
231
- wrapWithFigure(state, range, checkTokenTagName, caption, false, sp, opt)
306
+ rRange.end = en
307
+ checkCaption(tokens, n, en, rCaption, fNum, rSp, opt, TokenConstructor)
308
+ if (rCaption.isPrev || rCaption.isNext) {
309
+ wrapWithFigure(tokens, rRange, checkTokenTagName, rCaption, false, rSp, opt, TokenConstructor)
232
310
  }
233
311
  break
234
312
  }
@@ -237,20 +315,20 @@ const figureWithCaption = (state, opt) => {
237
315
  if (token.tag === 'code' && token.block) {
238
316
  checkToken = true
239
317
  let isSamp = false
240
- if (/^ *(?:samp|shell|console)(?:(?= )|$)/.test(token.info)) {
318
+ if (sampLangReg.test(token.info)) {
241
319
  token.tag = 'samp'
242
320
  isSamp = true
243
321
  }
244
322
  if (isSamp) {
245
323
  checkTokenTagName = 'pre-samp'
246
- caption.name = 'pre-samp'
324
+ rCaption.name = 'pre-samp'
247
325
  } else {
248
326
  checkTokenTagName = 'pre-code'
249
- caption.name = 'pre-code'
327
+ rCaption.name = 'pre-code'
250
328
  }
251
- checkCaption(state, n, en, caption, fNum, sp, opt)
252
- if (caption.isPrev || caption.isNext) {
253
- wrapWithFigure(state, range, checkTokenTagName, caption, false, sp, opt)
329
+ checkCaption(tokens, n, en, rCaption, fNum, rSp, opt, TokenConstructor)
330
+ if (rCaption.isPrev || rCaption.isNext) {
331
+ wrapWithFigure(tokens, rRange, checkTokenTagName, rCaption, false, rSp, opt, TokenConstructor)
254
332
  break
255
333
  }
256
334
  }
@@ -260,20 +338,19 @@ const figureWithCaption = (state, opt) => {
260
338
  }
261
339
 
262
340
  if (token.type === 'html_block') {
263
- const tags = ['video', 'audio', 'iframe', 'blockquote', 'div']
264
341
  let ctj = 0
265
342
  let hasTag
266
- while (ctj < tags.length) {
267
- if (tags[ctj] === 'div') {
343
+ while (ctj < htmlTags.length) {
344
+ if (htmlTags[ctj] === 'div') {
268
345
  // for vimeo
269
- hasTag = token.content.match(new RegExp('^<'+ tags[ctj] + ' ?[^>]*?><iframe[^>]*?>[\\s\\S]*?<\\/iframe><\\/' + tags[ctj] + '>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)'))
270
- tags[ctj] = 'iframe'
271
- sp.isVideoIframe = true
346
+ hasTag = token.content.match(getHtmlReg('div'))
347
+ htmlTags[ctj] = 'iframe'
348
+ rSp.isVideoIframe = true
272
349
  } else {
273
- hasTag = token.content.match(new RegExp('^<'+ tags[ctj] + ' ?[^>]*?>[\\s\\S]*?<\\/' + tags[ctj] + '>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)'))
350
+ hasTag = token.content.match(getHtmlReg(htmlTags[ctj]))
274
351
  }
275
- const blueskyContMatch = token.content.match(new RegExp('^<blockquote class="bluesky-embed"[^]*?>[\\s\\S]*$'))
276
- if (!(hasTag || (blueskyContMatch && tags[ctj] === 'blockquote'))) {
352
+ const blueskyContMatch = token.content.match(blueskyEmbedReg)
353
+ if (!(hasTag || (blueskyContMatch && htmlTags[ctj] === 'blockquote'))) {
277
354
  ctj++
278
355
  continue
279
356
  }
@@ -282,45 +359,45 @@ const figureWithCaption = (state, opt) => {
282
359
  token.content += '\n'
283
360
  }
284
361
  } else if (blueskyContMatch) {
285
- let addedCont = '';
362
+ let addedCont = ''
363
+ const tokensChildren = tokens
364
+ const tokensLength = tokensChildren.length
286
365
  let j = n + 1
287
366
  let hasEndBlockquote = true
288
- while (j < state.tokens.length) {
289
- const nextToken = state.tokens[j]
290
- if (nextToken.type === 'inline' && /<\/blockquote> *<script[^>]*?><\/script>$/.test(nextToken.content)) {
367
+ while (j < tokensLength) {
368
+ const nextToken = tokens[j]
369
+ if (nextToken.type === 'inline' && endBlockquoteScriptReg.test(nextToken.content)) {
291
370
  addedCont += nextToken.content + '\n'
292
- if (state.tokens[j + 1] && state.tokens[j + 1].type === 'paragraph_close') {
293
- state.tokens.splice(j + 1, 1)
371
+ if (tokens[j + 1] && tokens[j + 1].type === 'paragraph_close') {
372
+ tokens.splice(j + 1, 1)
294
373
  }
295
- state.tokens[j].content = ''
296
- state.tokens[j].children.forEach((child, i) => {
374
+ nextToken.content = ''
375
+ nextToken.children.forEach((child) => {
297
376
  child.content = ''
298
377
  })
299
378
  break
300
379
  }
301
380
  if (nextToken.type === 'paragraph_open') {
302
381
  addedCont += '\n'
303
- state.tokens.splice(j, 1)
382
+ tokens.splice(j, 1)
304
383
  continue
305
384
  }
306
- j++;
385
+ j++
307
386
  }
308
- token.content += addedCont;
387
+ token.content += addedCont
309
388
  if (!hasEndBlockquote) {
310
389
  ctj++
311
390
  continue
312
391
  }
313
392
  }
314
393
 
315
- checkTokenTagName = tags[ctj]
316
- caption.name = tags[ctj]
394
+ checkTokenTagName = htmlTags[ctj]
395
+ rCaption.name = htmlTags[ctj]
317
396
  checkToken = true
318
397
  if (checkTokenTagName === 'blockquote') {
319
- const classNameReg = /^<[^>]*? class="(twitter-tweet|instagram-media|text-post-media|bluesky-embed|mastodon-embed)"/
320
398
  const isIframeTypeBlockquote = token.content.match(classNameReg)
321
- //console.log(isIframeTypeBlockquote)
322
399
  if(isIframeTypeBlockquote) {
323
- sp.isIframeTypeBlockquote = true
400
+ rSp.isIframeTypeBlockquote = true
324
401
  } else {
325
402
  ctj++
326
403
  continue
@@ -330,19 +407,19 @@ const figureWithCaption = (state, opt) => {
330
407
  }
331
408
  if (!checkToken) {n++; continue;}
332
409
  if (checkTokenTagName === 'iframe') {
333
- if(/^<[^>]*? src="https:\/\/(?:www.youtube-nocookie.com|player.vimeo.com)\//i.test(token.content)) {
334
- sp.isVideoIframe = true
410
+ if(videoIframeReg.test(token.content)) {
411
+ rSp.isVideoIframe = true
335
412
  }
336
413
  }
337
414
 
338
- checkCaption(state, n, en, caption, fNum, sp, opt)
339
- if (caption.isPrev || caption.isNext) {
340
- wrapWithFigure(state, range, checkTokenTagName, caption, false, sp, opt)
415
+ checkCaption(tokens, n, en, rCaption, fNum, rSp, opt, TokenConstructor)
416
+ if (rCaption.isPrev || rCaption.isNext) {
417
+ wrapWithFigure(tokens, rRange, checkTokenTagName, rCaption, false, rSp, opt, TokenConstructor)
341
418
  n = en + 2
342
419
  } else if ((opt.iframeWithoutCaption && (checkTokenTagName === 'iframe')) ||
343
420
  (opt.videoWithoutCaption && (checkTokenTagName === 'video')) ||
344
421
  (opt.iframeTypeBlockquoteWithoutCaption && (checkTokenTagName === 'blockquote'))) {
345
- wrapWithFigure(state, range, checkTokenTagName, caption, false, sp, opt)
422
+ wrapWithFigure(tokens, rRange, checkTokenTagName, rCaption, false, rSp, opt, TokenConstructor)
346
423
  n = en + 2
347
424
  }
348
425
  }
@@ -353,27 +430,30 @@ const figureWithCaption = (state, opt) => {
353
430
  let isMultipleImagesHorizontal = true
354
431
  let isMultipleImagesVertical = true
355
432
  checkToken = true
356
- caption.name = 'img'
357
- while (ntChildTokenIndex < nextToken.children.length) {
358
- const ntChildToken = nextToken.children[ntChildTokenIndex]
359
- if (ntChildTokenIndex === nextToken.children.length - 1) {
360
- let imageAttrs = ntChildToken.content.match(/^ *\{(.*?)\} *$/)
433
+ rCaption.name = 'img'
434
+ const children = nextToken.children
435
+ const childrenLength = children.length
436
+ while (ntChildTokenIndex < childrenLength) {
437
+ const ntChildToken = children[ntChildTokenIndex]
438
+ if (ntChildTokenIndex === childrenLength - 1) {
439
+ let imageAttrs = ntChildToken.content.match(imageAttrsReg)
361
440
  if(ntChildToken.type === 'text' && imageAttrs) {
362
441
  imageAttrs = imageAttrs[1].split(/ +/)
363
442
  let iai = 0
364
- while (iai < imageAttrs.length) {
365
- if (/^\./.test(imageAttrs[iai])) {
366
- imageAttrs[iai] = imageAttrs[iai].replace(/^\./, "class=")
443
+ const attrsLength = imageAttrs.length
444
+ while (iai < attrsLength) {
445
+ if (classAttrReg.test(imageAttrs[iai])) {
446
+ imageAttrs[iai] = imageAttrs[iai].replace(classAttrReg, "class=")
367
447
  }
368
- if (/^#/.test(imageAttrs[iai])) {
369
- imageAttrs[iai] = imageAttrs[iai].replace(/^\#/, "id=")
448
+ if (idAttrReg.test(imageAttrs[iai])) {
449
+ imageAttrs[iai] = imageAttrs[iai].replace(idAttrReg, "id=")
370
450
  }
371
- let imageAttr = imageAttrs[iai].match(/^(.*?)="?(.*)"?$/)
451
+ let imageAttr = imageAttrs[iai].match(attrParseReg)
372
452
  if (!imageAttr || !imageAttr[1]) {
373
453
  iai++
374
454
  continue
375
455
  }
376
- sp.attrs.push([imageAttr[1], imageAttr[2]])
456
+ rSp.attrs.push([imageAttr[1], imageAttr[2]])
377
457
  iai++
378
458
  }
379
459
  break
@@ -386,7 +466,7 @@ const figureWithCaption = (state, opt) => {
386
466
  }
387
467
  if (ntChildToken.type === 'image') {
388
468
  imageNum += 1
389
- } else if (ntChildToken.type === 'text' && /^ *$/.test(ntChildToken.content)) {
469
+ } else if (ntChildToken.type === 'text' && whitespaceReg.test(ntChildToken.content)) {
390
470
  isMultipleImagesVertical = false
391
471
  if (isMultipleImagesVertical) {
392
472
  isMultipleImagesHorizontal = false
@@ -404,50 +484,58 @@ const figureWithCaption = (state, opt) => {
404
484
  }
405
485
  if (checkToken && imageNum > 1 && opt.multipleImages) {
406
486
  if (isMultipleImagesHorizontal) {
407
- caption.nameSuffix = '-horizontal'
487
+ rCaption.nameSuffix = '-horizontal'
408
488
  } else if (isMultipleImagesVertical) {
409
- caption.nameSuffix = '-vertical'
489
+ rCaption.nameSuffix = '-vertical'
410
490
  } else {
411
- caption.nameSuffix = '-multiple'
491
+ rCaption.nameSuffix = '-multiple'
412
492
  }
413
493
  ntChildTokenIndex = 0
414
- while (ntChildTokenIndex < nextToken.children.length) {
415
- const ccToken = nextToken.children[ntChildTokenIndex]
416
- if (ccToken.type === 'text' && /^ *$/.test(ccToken.content)) {
494
+ while (ntChildTokenIndex < childrenLength) {
495
+ const ccToken = children[ntChildTokenIndex]
496
+ if (ccToken.type === 'text' && whitespaceReg.test(ccToken.content)) {
417
497
  ccToken.content = ''
418
498
  }
419
499
  ntChildTokenIndex++
420
500
  }
421
501
  }
422
502
  en = n + 2
423
- range.end = en
503
+ rRange.end = en
424
504
  checkTokenTagName = 'img'
425
505
  nextToken.children[0].type = 'image'
426
506
 
427
- if (opt.imgAltCaption) setAltToLabel(state, n)
428
- if (opt.imgTitleCaption) setTitleToLabel(state, n)
429
- checkCaption(state, n, en, caption, fNum, sp, opt)
507
+ if (opt.imgAltCaption) setAltToLabel({ tokens, Token: TokenConstructor }, n)
508
+ if (opt.imgTitleCaption) setTitleToLabel({ tokens, Token: TokenConstructor }, n)
509
+ checkCaption(tokens, n, en, rCaption, fNum, rSp, opt, TokenConstructor)
430
510
 
431
- if (opt.oneImageWithoutCaption && state.tokens[n-1]) {
432
- if (state.tokens[n-1].type === 'list_item_open') checkToken = false
511
+ if (parentType === 'list_item' || isInListItem(tokens, n)) {
512
+ const isInTightList = token.hidden === true
513
+ if (isInTightList) {
514
+ checkToken = false
515
+ } else {
516
+ if (!opt.oneImageWithoutCaption && !rCaption.isPrev && !rCaption.isNext) {
517
+ checkToken = false
518
+ }
519
+ }
433
520
  }
434
- if (checkToken && (opt.oneImageWithoutCaption || caption.isPrev || caption.isNext)) {
435
- if (caption.nameSuffix) checkTokenTagName += caption.nameSuffix
436
- wrapWithFigure(state, range, checkTokenTagName, caption, true, sp, opt)
521
+
522
+ if (checkToken && (opt.oneImageWithoutCaption || rCaption.isPrev || rCaption.isNext)) {
523
+ if (rCaption.nameSuffix) checkTokenTagName += rCaption.nameSuffix
524
+ wrapWithFigure(tokens, rRange, checkTokenTagName, rCaption, true, rSp, opt, TokenConstructor)
437
525
  }
438
526
  }
439
527
 
440
- if (!checkToken || !caption.name) {n++; continue;}
528
+ if (!checkToken || !rCaption.name) {n++; continue;}
441
529
 
442
- n = range.start
443
- en = range.end
444
- if (caption.isPrev) {
445
- changePrevCaptionPosition(state, n, caption, opt)
530
+ n = rRange.start
531
+ en = rRange.end
532
+ if (rCaption.isPrev) {
533
+ changePrevCaptionPosition(tokens, n, rCaption, opt)
446
534
  n = en + 1
447
535
  continue
448
536
  }
449
- if (caption.isNext) {
450
- changeNextCaptionPosition(state, en, caption)
537
+ if (rCaption.isNext) {
538
+ changeNextCaptionPosition(tokens, en, rCaption)
451
539
  n = en + 4
452
540
  continue
453
541
  }
@@ -456,6 +544,36 @@ const figureWithCaption = (state, opt) => {
456
544
  return
457
545
  }
458
546
 
547
+ const isInListItem = (() => {
548
+ const cache = new WeakMap()
549
+ return (tokens, idx) => {
550
+ if (cache.has(tokens)) {
551
+ const cachedResult = cache.get(tokens)
552
+ if (cachedResult[idx] !== undefined) {
553
+ return cachedResult[idx]
554
+ }
555
+ } else {
556
+ cache.set(tokens, {})
557
+ }
558
+
559
+ const result = cache.get(tokens)
560
+
561
+ for (let i = idx - 1; i >= 0; i--) {
562
+ if (tokens[i].type === 'list_item_open') {
563
+ result[idx] = true
564
+ return true
565
+ }
566
+ if (tokens[i].type === 'list_item_close' || tokens[i].type === 'list_open') {
567
+ result[idx] = false
568
+ return false
569
+ }
570
+ }
571
+
572
+ result[idx] = false
573
+ return false
574
+ }
575
+ })()
576
+
459
577
  const mditFigureWithPCaption = (md, option) => {
460
578
  let opt = {
461
579
  classPrefix: 'f',
@@ -488,10 +606,9 @@ const mditFigureWithPCaption = (md, option) => {
488
606
  opt.oneImageWithoutCaption = true
489
607
  opt.multipleImages = false
490
608
  if (opt.setFigureNumber) {
491
- for (let mark of opt.removeUnnumberedLabelExceptMarks) {
492
- if (mark === 'img') opt.removeUnnumberedLabelExceptMarks.splice(opt.removeUnnumberedLabelExceptMarks.indexOf(mark), 1)
493
- if (mark === 'table') opt.removeUnnumberedLabelExceptMarks.splice(opt.removeUnnumberedLabelExceptMarks.indexOf(mark), 1)
494
- }
609
+ opt.removeUnnumberedLabelExceptMarks = opt.removeUnnumberedLabelExceptMarks.filter(
610
+ mark => mark !== 'img' && mark !== 'table'
611
+ )
495
612
  }
496
613
  md.block.ruler.before('paragraph', 'img_attr_caption', (state) => {
497
614
  imgAttrToPCaption(state, state.line, opt)
@@ -504,4 +621,4 @@ const mditFigureWithPCaption = (md, option) => {
504
621
  })
505
622
  }
506
623
 
507
- export default mditFigureWithPCaption
624
+ export default mditFigureWithPCaption