@peaceroad/markdown-it-figure-with-p-caption 0.12.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/README.md CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  This is a markdown-it plugin.
4
4
 
5
- For a paragraph with one image/images only, a table or code block or a blockquote or a iframe, and by writing a caption paragraph immediately before or after, they are converted into the figure element with the figcaption element.
5
+ For paragraphs containing only images, tables, code blocks, blockquotes, or iframes, this plugin converts them into figure elements with figcaption elements when a caption paragraph is written immediately before or after.
6
6
 
7
- 1. Check that the element: one image only paragraph, table, code(samp) block, blockquote, and video, iframe.
8
- 2. Check if this element has a caption paragraph immediately before or after it
9
- 3. If there is the caption paragraph, convert them to figure and figcaption element.
7
+ The conversion process:
8
+ 1. Detect supported elements: image paragraphs, tables, code/samp blocks, blockquotes, videos, and iframes
9
+ 2. Check for caption paragraphs immediately before or after the element
10
+ 3. Convert both elements into a figure with figcaption structure
10
11
 
11
12
  The figcaption behavior of this plugin depends on [p7d-markdown-it-p-captions](https://www.npmjs.com/package/p7d-markdown-it-p-captions).
12
13
 
@@ -36,6 +37,11 @@ Also, It is recommended to set the width and height attributes of the images at
36
37
 
37
38
  It could be applied to table, codeblock(pre > code, pre > samp), video as well.
38
39
 
40
+ These elements are also supported within the following structure. [0.13.0+]
41
+
42
+ - Blockquote
43
+ - loose list (with blank lines between items), not tight list (no blank lines)
44
+ - Description list block (`<dl>` markup, markdown-it-deflist)
39
45
 
40
46
  ## Example
41
47
 
package/index.js CHANGED
@@ -1,15 +1,24 @@
1
1
  import { setCaptionParagraph } from 'p7d-markdown-it-p-captions'
2
2
  import { imgAttrToPCaption, setAltToLabel, setTitleToLabel } from './imgAttrToPCaption.js'
3
3
 
4
- const htmlRegCache = {}
4
+ const htmlRegCache = 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 whitespaceReg = /^ *$/
14
+ const sampLangReg = /^ *(?:samp|shell|console)(?:(?= )|$)/
15
+ const endBlockquoteScriptReg = /<\/blockquote> *<script[^>]*?><\/script>$/
7
16
 
8
17
  const getHtmlReg = (tag) => {
9
- if (htmlRegCache[tag]) return htmlRegCache[tag]
10
- let regexStr = '^<' + tag + ' ?[^>]*?>[\\s\\S]*?<\\/' + tag + '>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)'
18
+ if (htmlRegCache.has(tag)) return htmlRegCache.get(tag)
19
+ const regexStr = `^<${tag} ?[^>]*?>[\\s\\S]*?<\\/${tag}>(\\n| *?)(<script [^>]*?>(?:<\\/script>)?)? *(\\n|$)`
11
20
  const reg = new RegExp(regexStr)
12
- htmlRegCache[tag] = reg
21
+ htmlRegCache.set(tag, reg)
13
22
  return reg
14
23
  }
15
24
 
@@ -26,13 +35,13 @@ const getCaptionName = (token) => {
26
35
  return ''
27
36
  }
28
37
 
29
- const checkPrevCaption = (state, n, caption, fNum, sp, opt) => {
38
+ const checkPrevCaption = (tokens, n, caption, fNum, sp, opt, TokenConstructor) => {
30
39
  if(n < 3) return caption
31
- const captionStartToken = state.tokens[n-3]
32
- const captionEndToken = state.tokens[n-1]
40
+ const captionStartToken = tokens[n-3]
41
+ const captionEndToken = tokens[n-1]
33
42
  if (captionStartToken === undefined || captionEndToken === undefined) return
34
43
  if (captionStartToken.type !== 'paragraph_open' && captionEndToken.type !== 'paragraph_close') return
35
- setCaptionParagraph(n-3, state, caption, fNum, sp, opt)
44
+ setCaptionParagraph(n-3, { tokens, Token: TokenConstructor }, caption, fNum, sp, opt)
36
45
  const captionName = getCaptionName(captionStartToken)
37
46
  if(!captionName) return
38
47
  caption.name = captionName
@@ -40,13 +49,13 @@ const checkPrevCaption = (state, n, caption, fNum, sp, opt) => {
40
49
  return
41
50
  }
42
51
 
43
- const checkNextCaption = (state, en, caption, fNum, sp, opt) => {
44
- if (en + 2 > state.tokens.length) return
45
- const captionStartToken = state.tokens[en+1]
46
- const captionEndToken = state.tokens[en+3]
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]
47
56
  if (captionStartToken === undefined || captionEndToken === undefined) return
48
57
  if (captionStartToken.type !== 'paragraph_open' && captionEndToken.type !== 'paragraph_close') return
49
- setCaptionParagraph(en+1, state, caption, fNum, sp, opt)
58
+ setCaptionParagraph(en+1, { tokens, Token: TokenConstructor }, caption, fNum, sp, opt)
50
59
  const captionName = getCaptionName(captionStartToken)
51
60
  if(!captionName) return
52
61
  caption.name = captionName
@@ -54,9 +63,15 @@ const checkNextCaption = (state, en, caption, fNum, sp, opt) => {
54
63
  return
55
64
  }
56
65
 
66
+ const cleanCaptionRegCache = new Map()
67
+
57
68
  const cleanCaptionTokenAttrs = (token, captionName) => {
58
- const reg = new RegExp(' *?f-' + captionName)
59
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
+ }
60
75
  for (let i = token.attrs.length - 1; i >= 0; i--) {
61
76
  if (token.attrs[i][0] === 'class') {
62
77
  token.attrs[i][1] = token.attrs[i][1].replace(reg, '').trim()
@@ -65,10 +80,10 @@ const cleanCaptionTokenAttrs = (token, captionName) => {
65
80
  }
66
81
  }
67
82
 
68
- const changePrevCaptionPosition = (state, n, caption, opt) => {
69
- const captionStartToken = state.tokens[n-3]
70
- const captionInlineToken = state.tokens[n-2]
71
- const captionEndToken = state.tokens[n-1]
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]
72
87
 
73
88
  if (opt.imgAltCaption || opt.imgTitleCaption) {
74
89
  let isNoCaption = false
@@ -83,7 +98,7 @@ const changePrevCaptionPosition = (state, n, caption, opt) => {
83
98
  }
84
99
  }
85
100
  if (isNoCaption) {
86
- state.tokens.splice(n-3, 3)
101
+ tokens.splice(n-3, 3)
87
102
  return false
88
103
  }
89
104
  }
@@ -93,29 +108,29 @@ const changePrevCaptionPosition = (state, n, caption, opt) => {
93
108
  captionStartToken.tag = 'figcaption'
94
109
  captionEndToken.type = 'figcaption_close'
95
110
  captionEndToken.tag = 'figcaption'
96
- state.tokens.splice(n + 2, 0, captionStartToken, captionInlineToken, captionEndToken)
97
- state.tokens.splice(n-3, 3)
111
+ tokens.splice(n + 2, 0, captionStartToken, captionInlineToken, captionEndToken)
112
+ tokens.splice(n-3, 3)
98
113
  return true
99
114
  }
100
115
 
101
- const changeNextCaptionPosition = (state, en, caption) => {
102
- const captionStartToken = state.tokens[en+2] // +1: text node for figure.
103
- const captionInlineToken = state.tokens[en+3]
104
- const captionEndToken = state.tokens[en+4]
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]
105
120
  cleanCaptionTokenAttrs(captionStartToken, caption.name)
106
121
  captionStartToken.type = 'figcaption_open'
107
122
  captionStartToken.tag = 'figcaption'
108
123
  captionEndToken.type = 'figcaption_close'
109
124
  captionEndToken.tag = 'figcaption'
110
- state.tokens.splice(en, 0, captionStartToken, captionInlineToken, captionEndToken)
111
- state.tokens.splice(en+5, 3)
125
+ tokens.splice(en, 0, captionStartToken, captionInlineToken, captionEndToken)
126
+ tokens.splice(en+5, 3)
112
127
  return true
113
128
  }
114
129
 
115
- const wrapWithFigure = (state, range, checkTokenTagName, caption, replaceInsteadOfWrap, sp, opt) => {
130
+ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInsteadOfWrap, sp, opt, TokenConstructor) => {
116
131
  let n = range.start
117
132
  let en = range.end
118
- const figureStartToken = new state.Token('figure_open', 'figure', 1)
133
+ const figureStartToken = new TokenConstructor('figure_open', 'figure', 1)
119
134
  figureStartToken.attrSet('class', 'f-' + checkTokenTagName)
120
135
 
121
136
  if (opt.allIframeTypeFigureClassName === '') {
@@ -143,8 +158,8 @@ const wrapWithFigure = (state, range, checkTokenTagName, caption, replaceInstead
143
158
  if(/pre-(?:code|samp)/.test(checkTokenTagName) && opt.roleDocExample) {
144
159
  figureStartToken.attrSet('role', 'doc-example')
145
160
  }
146
- const figureEndToken = new state.Token('figure_close', 'figure', -1)
147
- const breakToken = new state.Token('text', '', 0)
161
+ const figureEndToken = new TokenConstructor('figure_close', 'figure', -1)
162
+ const breakToken = new TokenConstructor('text', '', 0)
148
163
  breakToken.content = '\n'
149
164
  if (opt.styleProcess && caption.isNext && sp.attrs.length > 0) {
150
165
  for (let attr of sp.attrs) {
@@ -152,18 +167,18 @@ const wrapWithFigure = (state, range, checkTokenTagName, caption, replaceInstead
152
167
  }
153
168
  }
154
169
  // For vsce
155
- if(state.tokens[n].attrs && caption.name === 'img') {
156
- for (let attr of state.tokens[n].attrs) {
170
+ if(tokens[n].attrs && caption.name === 'img') {
171
+ for (let attr of tokens[n].attrs) {
157
172
  figureStartToken.attrJoin(attr[0], attr[1])
158
173
  }
159
174
  }
160
175
  if (replaceInsteadOfWrap) {
161
- state.tokens.splice(en, 1, breakToken, figureEndToken, breakToken)
162
- state.tokens.splice(n, 1, figureStartToken, breakToken)
176
+ tokens.splice(en, 1, breakToken, figureEndToken, breakToken)
177
+ tokens.splice(n, 1, figureStartToken, breakToken)
163
178
  en = en + 2
164
179
  } else {
165
- state.tokens.splice(en+1, 0, figureEndToken, breakToken)
166
- state.tokens.splice(n, 0, figureStartToken, breakToken)
180
+ tokens.splice(en+1, 0, figureEndToken, breakToken)
181
+ tokens.splice(n, 0, figureStartToken, breakToken)
167
182
  en = en + 3
168
183
  }
169
184
  range.start = n
@@ -171,46 +186,101 @@ const wrapWithFigure = (state, range, checkTokenTagName, caption, replaceInstead
171
186
  return
172
187
  }
173
188
 
174
- const checkCaption = (state, n, en, caption, fNum, sp, opt) => {
175
- 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)
176
191
  if (caption.isPrev) return
177
- checkNextCaption(state, en, caption, fNum, sp, opt)
192
+ checkNextCaption(tokens, en, caption, fNum, sp, opt, TokenConstructor)
178
193
  return
179
194
  }
180
195
 
181
- const figureWithCaption = (state, opt) => {
182
- const tokens = state.tokens
183
- const checkTypes = ['table', 'pre', 'blockquote']
184
- const htmlTags = ['video', 'audio', 'iframe', 'blockquote', 'div']
196
+ const processTokensRecursively = (tokens, opt, fNum, TokenConstructor, parentType) => {
197
+ const nestedContainers = ['blockquote', 'list_item', 'dd']
185
198
 
186
- let n = 0
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
+
243
+ const figureWithCaption = (state, opt) => {
187
244
  let fNum = {
188
245
  img: 0,
189
246
  table: 0,
190
247
  }
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
191
265
  while (n < tokens.length) {
192
266
  const token = tokens[n]
193
267
  const nextToken = tokens[n+1]
194
268
  let en = n
195
- let range = {
196
- start: n,
197
- end: en,
198
- }
269
+
270
+ rRange.start = n
271
+ rRange.end = en
199
272
  let checkToken = false
200
273
  let checkTokenTagName = ''
201
- let caption = {
202
- mark: '',
203
- name: '',
204
- nameSuffix: '',
205
- isPrev: false,
206
- isNext: false,
207
- }
208
- const sp = {
209
- attrs: [],
210
- isVideoIframe: false,
211
- isIframeTypeBlockquote: false,
212
- hasImgCaption: false,
213
- }
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
214
284
 
215
285
  let cti = 0
216
286
  while (cti < checkTypes.length) {
@@ -221,11 +291,11 @@ const figureWithCaption = (state, opt) => {
221
291
  }
222
292
  checkToken = true
223
293
  checkTokenTagName = token.tag
224
- caption.name = checkTypes[cti]
294
+ rCaption.name = checkTypes[cti]
225
295
  if (checkTypes[cti] === 'pre') {
226
- if (tokens[n+1].tag === 'code') caption.mark = 'pre-code'
227
- if (tokens[n+1].tag === 'samp') caption.mark = 'pre-samp'
228
- 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
229
299
  }
230
300
  while (en < tokens.length) {
231
301
  if(tokens[en].type === checkTokenTagName + '_close') {
@@ -233,10 +303,10 @@ const figureWithCaption = (state, opt) => {
233
303
  }
234
304
  en++
235
305
  }
236
- range.end = en
237
- checkCaption(state, n, en, caption, fNum, sp, opt)
238
- if (caption.isPrev || caption.isNext) {
239
- 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)
240
310
  }
241
311
  break
242
312
  }
@@ -245,20 +315,20 @@ const figureWithCaption = (state, opt) => {
245
315
  if (token.tag === 'code' && token.block) {
246
316
  checkToken = true
247
317
  let isSamp = false
248
- if (/^ *(?:samp|shell|console)(?:(?= )|$)/.test(token.info)) {
318
+ if (sampLangReg.test(token.info)) {
249
319
  token.tag = 'samp'
250
320
  isSamp = true
251
321
  }
252
322
  if (isSamp) {
253
323
  checkTokenTagName = 'pre-samp'
254
- caption.name = 'pre-samp'
324
+ rCaption.name = 'pre-samp'
255
325
  } else {
256
326
  checkTokenTagName = 'pre-code'
257
- caption.name = 'pre-code'
327
+ rCaption.name = 'pre-code'
258
328
  }
259
- checkCaption(state, n, en, caption, fNum, sp, opt)
260
- if (caption.isPrev || caption.isNext) {
261
- 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)
262
332
  break
263
333
  }
264
334
  }
@@ -275,7 +345,7 @@ const figureWithCaption = (state, opt) => {
275
345
  // for vimeo
276
346
  hasTag = token.content.match(getHtmlReg('div'))
277
347
  htmlTags[ctj] = 'iframe'
278
- sp.isVideoIframe = true
348
+ rSp.isVideoIframe = true
279
349
  } else {
280
350
  hasTag = token.content.match(getHtmlReg(htmlTags[ctj]))
281
351
  }
@@ -296,7 +366,7 @@ const figureWithCaption = (state, opt) => {
296
366
  let hasEndBlockquote = true
297
367
  while (j < tokensLength) {
298
368
  const nextToken = tokens[j]
299
- if (nextToken.type === 'inline' && /<\/blockquote> *<script[^>]*?><\/script>$/.test(nextToken.content)) {
369
+ if (nextToken.type === 'inline' && endBlockquoteScriptReg.test(nextToken.content)) {
300
370
  addedCont += nextToken.content + '\n'
301
371
  if (tokens[j + 1] && tokens[j + 1].type === 'paragraph_close') {
302
372
  tokens.splice(j + 1, 1)
@@ -322,13 +392,12 @@ const figureWithCaption = (state, opt) => {
322
392
  }
323
393
 
324
394
  checkTokenTagName = htmlTags[ctj]
325
- caption.name = htmlTags[ctj]
395
+ rCaption.name = htmlTags[ctj]
326
396
  checkToken = true
327
397
  if (checkTokenTagName === 'blockquote') {
328
- const classNameReg = /^<[^>]*? class="(twitter-tweet|instagram-media|text-post-media|bluesky-embed|mastodon-embed)"/
329
398
  const isIframeTypeBlockquote = token.content.match(classNameReg)
330
399
  if(isIframeTypeBlockquote) {
331
- sp.isIframeTypeBlockquote = true
400
+ rSp.isIframeTypeBlockquote = true
332
401
  } else {
333
402
  ctj++
334
403
  continue
@@ -338,19 +407,19 @@ const figureWithCaption = (state, opt) => {
338
407
  }
339
408
  if (!checkToken) {n++; continue;}
340
409
  if (checkTokenTagName === 'iframe') {
341
- if(/^<[^>]*? src="https:\/\/(?:www.youtube-nocookie.com|player.vimeo.com)\//i.test(token.content)) {
342
- sp.isVideoIframe = true
410
+ if(videoIframeReg.test(token.content)) {
411
+ rSp.isVideoIframe = true
343
412
  }
344
413
  }
345
414
 
346
- checkCaption(state, n, en, caption, fNum, sp, opt)
347
- if (caption.isPrev || caption.isNext) {
348
- 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)
349
418
  n = en + 2
350
419
  } else if ((opt.iframeWithoutCaption && (checkTokenTagName === 'iframe')) ||
351
420
  (opt.videoWithoutCaption && (checkTokenTagName === 'video')) ||
352
421
  (opt.iframeTypeBlockquoteWithoutCaption && (checkTokenTagName === 'blockquote'))) {
353
- wrapWithFigure(state, range, checkTokenTagName, caption, false, sp, opt)
422
+ wrapWithFigure(tokens, rRange, checkTokenTagName, rCaption, false, rSp, opt, TokenConstructor)
354
423
  n = en + 2
355
424
  }
356
425
  }
@@ -361,30 +430,30 @@ const figureWithCaption = (state, opt) => {
361
430
  let isMultipleImagesHorizontal = true
362
431
  let isMultipleImagesVertical = true
363
432
  checkToken = true
364
- caption.name = 'img'
433
+ rCaption.name = 'img'
365
434
  const children = nextToken.children
366
435
  const childrenLength = children.length
367
436
  while (ntChildTokenIndex < childrenLength) {
368
437
  const ntChildToken = children[ntChildTokenIndex]
369
438
  if (ntChildTokenIndex === childrenLength - 1) {
370
- let imageAttrs = ntChildToken.content.match(/^ *\{(.*?)\} *$/)
439
+ let imageAttrs = ntChildToken.content.match(imageAttrsReg)
371
440
  if(ntChildToken.type === 'text' && imageAttrs) {
372
441
  imageAttrs = imageAttrs[1].split(/ +/)
373
442
  let iai = 0
374
443
  const attrsLength = imageAttrs.length
375
444
  while (iai < attrsLength) {
376
- if (/^\./.test(imageAttrs[iai])) {
377
- imageAttrs[iai] = imageAttrs[iai].replace(/^\./, "class=")
445
+ if (classAttrReg.test(imageAttrs[iai])) {
446
+ imageAttrs[iai] = imageAttrs[iai].replace(classAttrReg, "class=")
378
447
  }
379
- if (/^#/.test(imageAttrs[iai])) {
380
- imageAttrs[iai] = imageAttrs[iai].replace(/^#/, "id=")
448
+ if (idAttrReg.test(imageAttrs[iai])) {
449
+ imageAttrs[iai] = imageAttrs[iai].replace(idAttrReg, "id=")
381
450
  }
382
- let imageAttr = imageAttrs[iai].match(/^(.*?)="?(.*)"?$/)
451
+ let imageAttr = imageAttrs[iai].match(attrParseReg)
383
452
  if (!imageAttr || !imageAttr[1]) {
384
453
  iai++
385
454
  continue
386
455
  }
387
- sp.attrs.push([imageAttr[1], imageAttr[2]])
456
+ rSp.attrs.push([imageAttr[1], imageAttr[2]])
388
457
  iai++
389
458
  }
390
459
  break
@@ -397,7 +466,7 @@ const figureWithCaption = (state, opt) => {
397
466
  }
398
467
  if (ntChildToken.type === 'image') {
399
468
  imageNum += 1
400
- } else if (ntChildToken.type === 'text' && /^ *$/.test(ntChildToken.content)) {
469
+ } else if (ntChildToken.type === 'text' && whitespaceReg.test(ntChildToken.content)) {
401
470
  isMultipleImagesVertical = false
402
471
  if (isMultipleImagesVertical) {
403
472
  isMultipleImagesHorizontal = false
@@ -415,50 +484,58 @@ const figureWithCaption = (state, opt) => {
415
484
  }
416
485
  if (checkToken && imageNum > 1 && opt.multipleImages) {
417
486
  if (isMultipleImagesHorizontal) {
418
- caption.nameSuffix = '-horizontal'
487
+ rCaption.nameSuffix = '-horizontal'
419
488
  } else if (isMultipleImagesVertical) {
420
- caption.nameSuffix = '-vertical'
489
+ rCaption.nameSuffix = '-vertical'
421
490
  } else {
422
- caption.nameSuffix = '-multiple'
491
+ rCaption.nameSuffix = '-multiple'
423
492
  }
424
493
  ntChildTokenIndex = 0
425
494
  while (ntChildTokenIndex < childrenLength) {
426
495
  const ccToken = children[ntChildTokenIndex]
427
- if (ccToken.type === 'text' && /^ *$/.test(ccToken.content)) {
496
+ if (ccToken.type === 'text' && whitespaceReg.test(ccToken.content)) {
428
497
  ccToken.content = ''
429
498
  }
430
499
  ntChildTokenIndex++
431
500
  }
432
501
  }
433
502
  en = n + 2
434
- range.end = en
503
+ rRange.end = en
435
504
  checkTokenTagName = 'img'
436
505
  nextToken.children[0].type = 'image'
437
506
 
438
- if (opt.imgAltCaption) setAltToLabel(state, n)
439
- if (opt.imgTitleCaption) setTitleToLabel(state, n)
440
- 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)
441
510
 
442
- if (opt.oneImageWithoutCaption && tokens[n-1]) {
443
- if (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
+ }
444
520
  }
445
- if (checkToken && (opt.oneImageWithoutCaption || caption.isPrev || caption.isNext)) {
446
- if (caption.nameSuffix) checkTokenTagName += caption.nameSuffix
447
- 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)
448
525
  }
449
526
  }
450
527
 
451
- if (!checkToken || !caption.name) {n++; continue;}
528
+ if (!checkToken || !rCaption.name) {n++; continue;}
452
529
 
453
- n = range.start
454
- en = range.end
455
- if (caption.isPrev) {
456
- changePrevCaptionPosition(state, n, caption, opt)
530
+ n = rRange.start
531
+ en = rRange.end
532
+ if (rCaption.isPrev) {
533
+ changePrevCaptionPosition(tokens, n, rCaption, opt)
457
534
  n = en + 1
458
535
  continue
459
536
  }
460
- if (caption.isNext) {
461
- changeNextCaptionPosition(state, en, caption)
537
+ if (rCaption.isNext) {
538
+ changeNextCaptionPosition(tokens, en, rCaption)
462
539
  n = en + 4
463
540
  continue
464
541
  }
@@ -467,6 +544,36 @@ const figureWithCaption = (state, opt) => {
467
544
  return
468
545
  }
469
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
+
470
577
  const mditFigureWithPCaption = (md, option) => {
471
578
  let opt = {
472
579
  classPrefix: 'f',
@@ -499,10 +606,9 @@ const mditFigureWithPCaption = (md, option) => {
499
606
  opt.oneImageWithoutCaption = true
500
607
  opt.multipleImages = false
501
608
  if (opt.setFigureNumber) {
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
- }
609
+ opt.removeUnnumberedLabelExceptMarks = opt.removeUnnumberedLabelExceptMarks.filter(
610
+ mark => mark !== 'img' && mark !== 'table'
611
+ )
506
612
  }
507
613
  md.block.ruler.before('paragraph', 'img_attr_caption', (state) => {
508
614
  imgAttrToPCaption(state, state.line, opt)
@@ -515,4 +621,4 @@ const mditFigureWithPCaption = (md, option) => {
515
621
  })
516
622
  }
517
623
 
518
- export default mditFigureWithPCaption
624
+ export default mditFigureWithPCaption
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peaceroad/markdown-it-figure-with-p-caption",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "A markdown-it plugin. For a paragraph with only one image, a table or code block or blockquote, and by writing a caption paragraph immediately before or after, they are converted into the figure element with the figcaption element.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -24,5 +24,11 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "p7d-markdown-it-p-captions": "^0.17.0"
27
- }
27
+ },
28
+ "files": [
29
+ "index.js",
30
+ "imgAttrToPCaption.js",
31
+ "README.md",
32
+ "LICENSE"
33
+ ]
28
34
  }
@@ -1,5 +0,0 @@
1
- {
2
- "cSpell.ignoreWords": [
3
- "figcaption"
4
- ]
5
- }