motion-markdown-it 12.0.6 → 13.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -4
  3. data/lib/motion-markdown-it/index.rb +2 -2
  4. data/lib/motion-markdown-it/parser_core.rb +4 -1
  5. data/lib/motion-markdown-it/parser_inline.rb +11 -2
  6. data/lib/motion-markdown-it/presets/commonmark.rb +3 -2
  7. data/lib/motion-markdown-it/presets/zero.rb +4 -3
  8. data/lib/motion-markdown-it/renderer.rb +3 -3
  9. data/lib/motion-markdown-it/rules_block/code.rb +1 -1
  10. data/lib/motion-markdown-it/rules_block/html_block.rb +1 -1
  11. data/lib/motion-markdown-it/rules_block/list.rb +6 -2
  12. data/lib/motion-markdown-it/rules_block/table.rb +1 -1
  13. data/lib/motion-markdown-it/rules_core/linkify.rb +11 -1
  14. data/lib/motion-markdown-it/rules_core/replacements.rb +2 -3
  15. data/lib/motion-markdown-it/rules_core/text_join.rb +51 -0
  16. data/lib/motion-markdown-it/rules_inline/balance_pairs.rb +35 -13
  17. data/lib/motion-markdown-it/rules_inline/emphasis.rb +4 -11
  18. data/lib/motion-markdown-it/rules_inline/entity.rb +27 -22
  19. data/lib/motion-markdown-it/rules_inline/escape.rb +41 -22
  20. data/lib/motion-markdown-it/rules_inline/{text_collapse.rb → fragments_join.rb} +23 -22
  21. data/lib/motion-markdown-it/rules_inline/html_inline.rb +11 -0
  22. data/lib/motion-markdown-it/rules_inline/link.rb +3 -1
  23. data/lib/motion-markdown-it/rules_inline/linkify.rb +60 -0
  24. data/lib/motion-markdown-it/rules_inline/newline.rb +7 -1
  25. data/lib/motion-markdown-it/rules_inline/state_inline.rb +5 -1
  26. data/lib/motion-markdown-it/rules_inline/strikethrough.rb +0 -1
  27. data/lib/motion-markdown-it/token.rb +1 -0
  28. data/lib/motion-markdown-it/version.rb +1 -1
  29. data/lib/motion-markdown-it.rb +4 -2
  30. data/spec/motion-markdown-it/misc_spec.rb +47 -0
  31. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74ae9a8bfdbc637f5e2127d34919298661b7d4f32b55830053a0c30012cd1ddf
4
- data.tar.gz: 5787ec92937c5e8ef19fead547c0fae14b0de4507c0098cb5e8a79720dab611e
3
+ metadata.gz: 527ad2365d9f847959cb068d6483944ece97a6f25198b159c342fd09d340e7f1
4
+ data.tar.gz: de08e3d6331f1fe3e359df0e935cead71a259bb211885084000e6b93ed9cd909
5
5
  SHA512:
6
- metadata.gz: 72214978791be3e608c627bfa80db56a1ca30df9650a1df85bef0c2a99fb68b822a1464261595779f16385a9e69cf9898fbd15966ba5e00ad969d4a0568c028a
7
- data.tar.gz: 5e2ede784f3386d45b930789abb2c0a05ee8bc2dffb707d736c437e48fe722b9084dc75f2032bea6a8d47b723d0daac43428fb059e1ac1f7c6fa92dfad4a83e7
6
+ metadata.gz: 7abfc3bd88c7f30e93826a1ae27a00d95c8f47f6f9e634a739ddf597c7a0ffdc73fd295fa353f0e306861e5301b0190aba2f8fd1e56e4ffee5d3c61383be7f45
7
+ data.tar.gz: 10896d6c58b7d9675b52833ef975c77441fb4fa7c295aa6f38325226dd10de80609c892a1969f431bb0ce4b12565feb36a9ab6bdbfe4277cd116f5e43379dd08
data/README.md CHANGED
@@ -7,7 +7,7 @@ Ruby/RubyMotion version of Markdown-it (CommonMark compliant and extendable)
7
7
 
8
8
  This gem is a port of the [markdown-it Javascript package](https://github.com/markdown-it/markdown-it) by Vitaly Puzrin and Alex Kocharin.
9
9
 
10
- _Currently synced with markdown-it 12.0.6_
10
+ _Currently synced with markdown-it 13.0.1_
11
11
 
12
12
  ---
13
13
 
@@ -185,7 +185,7 @@ var md = require('markdown-it')({
185
185
  highlight: function (str, lang) {
186
186
  if (lang && hljs.getLanguage(lang)) {
187
187
  try {
188
- return hljs.highlight(lang, str).value;
188
+ return hljs.highlight(str, { language: lang }).value;
189
189
  } catch (__) {}
190
190
  }
191
191
 
@@ -205,7 +205,7 @@ var md = require('markdown-it')({
205
205
  if (lang && hljs.getLanguage(lang)) {
206
206
  try {
207
207
  return '<pre class="hljs"><code>' +
208
- hljs.highlight(lang, str, true).value +
208
+ hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
209
209
  '</code></pre>';
210
210
  } catch (__) {}
211
211
  }
@@ -260,7 +260,7 @@ By default all rules are enabled, but can be restricted by options. On plugin
260
260
  load all its rules are enabled automatically.
261
261
 
262
262
  ```js
263
- // Activate/deactivate rules, with curring
263
+ // Activate/deactivate rules, with currying
264
264
  var md = require('markdown-it')()
265
265
  .disable([ 'link', 'image' ])
266
266
  .enable([ 'link' ])
@@ -190,7 +190,7 @@ module MarkdownIt
190
190
  # highlight: function (str, lang) {
191
191
  # if (lang && hljs.getLanguage(lang)) {
192
192
  # try {
193
- # return hljs.highlight(lang, str, true).value;
193
+ # return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value;
194
194
  # } catch (__) {}
195
195
  # }
196
196
  #
@@ -210,7 +210,7 @@ module MarkdownIt
210
210
  # if (lang && hljs.getLanguage(lang)) {
211
211
  # try {
212
212
  # return '<pre class="hljs"><code>' +
213
- # hljs.highlight(lang, str, true).value +
213
+ # hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
214
214
  # '</code></pre>';
215
215
  # } catch (__) {}
216
216
  # }
@@ -10,12 +10,15 @@ module MarkdownIt
10
10
  attr_accessor :ruler
11
11
 
12
12
  RULES = [
13
- [ 'normalize', lambda { |state| RulesCore::Normalize.normalize(state) } ],
13
+ [ 'normalize', lambda { |state| RulesCore::Normalize.normalize(state) } ],
14
14
  [ 'block', lambda { |state| RulesCore::Block.block(state) } ],
15
15
  [ 'inline', lambda { |state| RulesCore::Inline.inline(state) } ],
16
16
  [ 'linkify', lambda { |state| RulesCore::Linkify.linkify(state) } ],
17
17
  [ 'replacements', lambda { |state| RulesCore::Replacements.replace(state) } ],
18
18
  [ 'smartquotes', lambda { |state| RulesCore::Smartquotes.smartquotes(state) } ],
19
+ # `text_join` finds `text_special` tokens (for escape sequences)
20
+ # and joins them with the rest of the text
21
+ [ 'text_join', lambda { |state| RulesCore::TextJoin.text_join(state) } ],
19
22
  ]
20
23
 
21
24
 
@@ -13,6 +13,7 @@ module MarkdownIt
13
13
 
14
14
  RULES = [
15
15
  [ 'text', lambda { |state, startLine| RulesInline::Text.text(state, startLine) } ],
16
+ [ 'linkify', lambda { |state, silent| RulesInline::Linkify.linkify(state, silent) } ],
16
17
  [ 'newline', lambda { |state, startLine| RulesInline::Newline.newline(state, startLine) } ],
17
18
  [ 'escape', lambda { |state, startLine| RulesInline::Escape.escape(state, startLine) } ],
18
19
  [ 'backticks', lambda { |state, startLine| RulesInline::Backticks.backtick(state, startLine) } ],
@@ -25,11 +26,19 @@ module MarkdownIt
25
26
  [ 'entity', lambda { |state, startLine| RulesInline::Entity.entity(state, startLine) } ],
26
27
  ]
27
28
 
29
+ # `rule2` ruleset was created specifically for emphasis/strikethrough
30
+ # post-processing and may be changed in the future.
31
+ #
32
+ # Don't use this for anything except pairs (plugins working with `balance_pairs`).
33
+ #
28
34
  RULES2 = [
29
35
  [ 'balance_pairs', lambda { |state| RulesInline::BalancePairs.link_pairs(state) } ],
30
36
  [ 'strikethrough', lambda { |state| RulesInline::Strikethrough.postProcess(state) } ],
31
37
  [ 'emphasis', lambda { |state| RulesInline::Emphasis.postProcess(state) } ],
32
- [ 'text_collapse', lambda { |state| RulesInline::TextCollapse.text_collapse(state) } ]
38
+ # [ 'text_collapse', lambda { |state| RulesInline::TextCollapse.text_collapse(state) } ]
39
+ # rules for pairs separate '**' into its own text tokens, which may be left unused,
40
+ # rule below merges unused segments back with the rest of the text
41
+ [ 'fragments_join', lambda { |state| RulesInline::FragmentsJoin.fragments_join(state) } ]
33
42
  ];
34
43
 
35
44
  #------------------------------------------------------------------------------
@@ -154,4 +163,4 @@ module MarkdownIt
154
163
  end
155
164
  end
156
165
  end
157
- end
166
+ end
@@ -39,7 +39,8 @@ module MarkdownIt
39
39
  rules: [
40
40
  'normalize',
41
41
  'block',
42
- 'inline'
42
+ 'inline',
43
+ 'text_join'
43
44
  ]
44
45
  },
45
46
 
@@ -74,7 +75,7 @@ module MarkdownIt
74
75
  rules2: [
75
76
  'balance_pairs',
76
77
  'emphasis',
77
- 'text_collapse'
78
+ 'fragments_join'
78
79
  ]
79
80
  }
80
81
  }
@@ -40,7 +40,8 @@ module MarkdownIt
40
40
  rules: [
41
41
  'normalize',
42
42
  'block',
43
- 'inline'
43
+ 'inline',
44
+ 'text_join'
44
45
  ]
45
46
  },
46
47
 
@@ -56,7 +57,7 @@ module MarkdownIt
56
57
  ],
57
58
  rules2: [
58
59
  'balance_pairs',
59
- 'text_collapse'
60
+ 'fragments_join'
60
61
  ]
61
62
  }
62
63
  }
@@ -64,4 +65,4 @@ module MarkdownIt
64
65
  end
65
66
  end
66
67
  end
67
- end
68
+ end
@@ -245,7 +245,7 @@ module MarkdownIt
245
245
 
246
246
 
247
247
  # Renderer.renderInline(tokens, options, env) -> String
248
- # - tokens (Array): list on block tokens to renter
248
+ # - tokens (Array): list on block tokens to render
249
249
  # - options (Object): params of parser instance
250
250
  # - env (Object): additional data from parsed input (references, for example)
251
251
  #
@@ -271,7 +271,7 @@ module MarkdownIt
271
271
 
272
272
  # internal
273
273
  # Renderer.renderInlineAsText(tokens, options, env) -> String
274
- # - tokens (Array): list on block tokens to renter
274
+ # - tokens (Array): list on block tokens to render
275
275
  # - options (Object): params of parser instance
276
276
  # - env (Object): additional data from parsed input (references, for example)
277
277
  #
@@ -297,7 +297,7 @@ module MarkdownIt
297
297
 
298
298
 
299
299
  # Renderer.render(tokens, options, env) -> String
300
- # - tokens (Array): list on block tokens to renter
300
+ # - tokens (Array): list on block tokens to render
301
301
  # - options (Object): params of parser instance
302
302
  # - env (Object): additional data from parsed input (references, for example)
303
303
  #
@@ -26,7 +26,7 @@ module MarkdownIt
26
26
  state.line = last
27
27
 
28
28
  token = state.push('code_block', 'code', 0)
29
- token.content = state.getLines(startLine, last, 4 + state.blkIndent, true)
29
+ token.content = state.getLines(startLine, last, 4 + state.blkIndent, false) + "\n"
30
30
  token.map = [ startLine, state.line ]
31
31
  return true
32
32
  end
@@ -11,7 +11,7 @@ module MarkdownIt
11
11
  # last argument defines whether it can terminate a paragraph or not
12
12
  #
13
13
  HTML_SEQUENCES = [
14
- [ /^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true ],
14
+ [ /^<(script|pre|style|textarea)(?=(\s|>|$))/i, /<\/(script|pre|style|textarea)>/i, true ],
15
15
  [ /^<!--/, /-->/, true ],
16
16
  [ /^<\?/, /\?>/, true ],
17
17
  [ /^<![A-Z]/, />/, true ],
@@ -129,7 +129,7 @@ module MarkdownIt
129
129
  # This code can fail if plugins use blkIndent as well as lists,
130
130
  # but I hope the spec gets fixed long before that happens.
131
131
  #
132
- if state.tShift[startLine] >= state.blkIndent
132
+ if state.sCount[startLine] >= state.blkIndent
133
133
  isTerminatingParagraph = true
134
134
  end
135
135
  end
@@ -138,7 +138,7 @@ module MarkdownIt
138
138
  if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0)
139
139
  isOrdered = true
140
140
  start = state.bMarks[startLine] + state.tShift[startLine]
141
- markerValue = state.src[start, posAfterMarker - start - 1].to_i
141
+ markerValue = state.src[start, posAfterMarker - 1].to_i
142
142
 
143
143
  # If we're starting a new ordered list right after
144
144
  # a paragraph, it should start with 1.
@@ -231,6 +231,9 @@ module MarkdownIt
231
231
  token = state.push('list_item_open', 'li', 1)
232
232
  token.markup = markerCharCode.chr
233
233
  token.map = itemLines = [ startLine, 0 ]
234
+ if (isOrdered)
235
+ token.info = state.src.slice(start...posAfterMarker - 1)
236
+ end
234
237
 
235
238
  # change current state, then restore it after parser subcall
236
239
  oldTight = state.tight
@@ -307,6 +310,7 @@ module MarkdownIt
307
310
  if (isOrdered)
308
311
  posAfterMarker = skipOrderedListMarker(state, nextLine)
309
312
  break if (posAfterMarker < 0)
313
+ start = state.bMarks[nextLine] + state.tShift[nextLine]
310
314
  else
311
315
  posAfterMarker = skipBulletListMarker(state, nextLine)
312
316
  break if (posAfterMarker < 0)
@@ -10,7 +10,7 @@ module MarkdownIt
10
10
  pos = state.bMarks[line] + state.tShift[line]
11
11
  max = state.eMarks[line]
12
12
 
13
- return state.src[pos, max - pos]
13
+ return state.src[pos...max]
14
14
  end
15
15
 
16
16
  #------------------------------------------------------------------------------
@@ -66,6 +66,16 @@ module MarkdownIt
66
66
  level = currentToken.level
67
67
  lastPos = 0
68
68
 
69
+ # forbid escape sequence at the start of the string,
70
+ # this avoids http\://example.com/ from being linkified as
71
+ # http:<a href="//example.com/">//example.com/</a>
72
+ if (links.length > 0 &&
73
+ links[0].index == 0 &&
74
+ i > 0 &&
75
+ tokens[i - 1].type == 'text_special')
76
+ links = links.slice(1..-1)
77
+ end
78
+
69
79
  (0...links.length).each do |ln|
70
80
  url = links[ln].url
71
81
  fullUrl = state.md.normalizeLink.call(url)
@@ -133,4 +143,4 @@ module MarkdownIt
133
143
 
134
144
  end
135
145
  end
136
- end
146
+ end
@@ -15,15 +15,14 @@ module MarkdownIt
15
15
 
16
16
  # TODO (from original)
17
17
  # - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾
18
- # - miltiplication 2 x 4 -> 2 × 4
18
+ # - multiplications 2 x 4 -> 2 × 4
19
19
 
20
20
  RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/
21
21
 
22
- SCOPED_ABBR_RE = /\((c|tm|r|p)\)/i
22
+ SCOPED_ABBR_RE = /\((c|tm|r)\)/i
23
23
  SCOPED_ABBR = {
24
24
  'c' => '©',
25
25
  'r' => '®',
26
- 'p' => '§',
27
26
  'tm' => '™'
28
27
  }
29
28
 
@@ -0,0 +1,51 @@
1
+ # Join raw text tokens with the rest of the text
2
+ #
3
+ # This is set as a separate rule to provide an opportunity for plugins
4
+ # to run text replacements after text join, but before escape join.
5
+ #
6
+ # For example, `\:)` shouldn't be replaced with an emoji.
7
+ #
8
+ module MarkdownIt
9
+ module RulesCore
10
+ class TextJoin
11
+ def self.text_join(state)
12
+ blockTokens = state.tokens
13
+
14
+ (0...blockTokens.length).each do |j|
15
+ next if (blockTokens[j].type != 'inline')
16
+
17
+ tokens = blockTokens[j].children
18
+ max = tokens.length
19
+
20
+ (0...max).each do |curr|
21
+ if (tokens[curr].type == 'text_special')
22
+ tokens[curr].type = 'text'
23
+ end
24
+ end
25
+
26
+ last = 0
27
+ curr = 0
28
+ while curr < max
29
+ if (tokens[curr].type == 'text' &&
30
+ curr + 1 < max &&
31
+ tokens[curr + 1].type == 'text')
32
+
33
+ # collapse two adjacent text nodes
34
+ tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content
35
+ else
36
+ tokens[last] = tokens[curr] if (curr != last)
37
+
38
+ last += 1
39
+ end
40
+
41
+ curr += 1
42
+ end
43
+
44
+ if (curr != last)
45
+ tokens.pop(tokens.length - last)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -9,9 +9,28 @@ module MarkdownIt
9
9
  openersBottom = {}
10
10
  max = delimiters.length
11
11
 
12
+ return if (!max)
13
+
14
+ # headerIdx is the first delimiter of the current (where closer is) delimiter run
15
+ headerIdx = 0
16
+ lastTokenIdx = -2 # needs any value lower than -1
17
+ jumps = []
18
+
12
19
  0.upto(max - 1) do |closerIdx|
13
20
  closer = delimiters[closerIdx]
14
21
 
22
+ jumps.push(0)
23
+
24
+ # markers belong to same delimiter run if:
25
+ # - they have adjacent tokens
26
+ # - AND markers are the same
27
+ #
28
+ if (delimiters[headerIdx][:marker] != closer[:marker] || lastTokenIdx != closer[:token] - 1)
29
+ headerIdx = closerIdx
30
+ end
31
+
32
+ lastTokenIdx = closer[:token]
33
+
15
34
  # Length is only used for emphasis-specific "rule of 3",
16
35
  # if it's not defined (in strikethrough or 3rd party plugins),
17
36
  # we can default it to 0 to disable those checks.
@@ -21,24 +40,23 @@ module MarkdownIt
21
40
  next if (!closer[:close])
22
41
 
23
42
  # Previously calculated lower bounds (previous fails)
24
- # for each marker and each delimiter length modulo 3.
43
+ # for each marker, each delimiter length modulo 3,
44
+ # and for whether this closer can be an opener;
45
+ # https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c72443460
25
46
  unless openersBottom[closer[:marker]]
26
- openersBottom[closer[:marker]] = [ -1, -1, -1 ]
47
+ openersBottom[closer[:marker]] = [ -1, -1, -1, -1, -1, -1 ]
27
48
  end
28
49
 
29
- minOpenerIdx = openersBottom[closer[:marker]][closer[:length] % 3]
50
+ minOpenerIdx = openersBottom[closer[:marker]][(closer[:open] ? 3 : 0) + (closer[:length] % 3)]
30
51
 
31
- openerIdx = closerIdx - closer[:jump] - 1
32
-
33
- # avoid crash if `closer.jump` is pointing outside of the array, see #742
34
- openerIdx = -1 if (openerIdx < -1)
52
+ openerIdx = headerIdx - jumps[headerIdx] - 1
35
53
 
36
54
  newMinOpenerIdx = openerIdx
37
55
 
38
56
  while openerIdx > minOpenerIdx
39
57
  opener = delimiters[openerIdx]
40
58
 
41
- (openerIdx -= opener[:jump] + 1) && next if (opener[:marker] != closer[:marker])
59
+ (openerIdx -= jumps[openerIdx] + 1) && next if (opener[:marker] != closer[:marker])
42
60
 
43
61
  if (opener[:open] && opener[:end] < 0)
44
62
 
@@ -65,19 +83,23 @@ module MarkdownIt
65
83
  # sure algorithm has linear complexity (see *_*_*_*_*_... case).
66
84
  #
67
85
  lastJump = openerIdx > 0 && !delimiters[openerIdx - 1][:open] ?
68
- delimiters[openerIdx - 1][:jump] + 1 : 0
86
+ jumps[openerIdx - 1] + 1 : 0
69
87
 
70
- closer[:jump] = closerIdx - openerIdx + lastJump
88
+ jumps[closerIdx] = closerIdx - openerIdx + lastJump
89
+ jumps[openerIdx] = lastJump
90
+
71
91
  closer[:open] = false
72
92
  opener[:end] = closerIdx
73
- opener[:jump] = lastJump
74
93
  opener[:close] = false
75
94
  newMinOpenerIdx = -1
95
+ # treat next token as start of run,
96
+ # it optimizes skips in **<...>**a**<...>** pathological case
97
+ lastTokenIdx = -2
76
98
  break
77
99
  end
78
100
  end
79
101
 
80
- openerIdx -= opener[:jump] + 1
102
+ openerIdx -= jumps[openerIdx] + 1
81
103
  end
82
104
 
83
105
  if (newMinOpenerIdx != -1)
@@ -88,7 +110,7 @@ module MarkdownIt
88
110
  # See details here:
89
111
  # https://github.com/commonmark/cmark/issues/178#issuecomment-270417442
90
112
  #
91
- openersBottom[closer[:marker]][(closer[:length] || 0) % 3] = newMinOpenerIdx
113
+ openersBottom[closer[:marker]][(closer[:open] ? 3 : 0) + ((closer[:length] || 0) % 3)] = newMinOpenerIdx
92
114
  end
93
115
  end
94
116
  end
@@ -30,15 +30,6 @@ module MarkdownIt
30
30
  #
31
31
  length: scanned[:length],
32
32
 
33
- # An amount of characters before this one that's equivalent to
34
- # current one. In plain English: if this delimiter does not open
35
- # an emphasis, neither do previous `jump` characters.
36
- #
37
- # Used to skip sequences like "*****" in one step, for 1st asterisk
38
- # value will be 0, for 2nd it's 1 and so on.
39
- #
40
- jump: i,
41
-
42
33
  # A position of the token this delimiter corresponds to.
43
34
  #
44
35
  token: state.tokens.length - 1,
@@ -82,9 +73,11 @@ module MarkdownIt
82
73
  #
83
74
  isStrong = i > 0 &&
84
75
  delimiters[i - 1][:end] == startDelim[:end] + 1 &&
76
+ # check that first two markers match and adjacent
77
+ delimiters[i - 1][:marker] == startDelim[:marker] &&
85
78
  delimiters[i - 1][:token] == startDelim[:token] - 1 &&
86
- delimiters[startDelim[:end] + 1][:token] == endDelim[:token] + 1 &&
87
- delimiters[i - 1][:marker] == startDelim[:marker]
79
+ # check that last two markers are adjacent (we can safely assume they match)
80
+ delimiters[startDelim[:end] + 1][:token] == endDelim[:token] + 1
88
81
 
89
82
  ch = fromCodePoint(startDelim[:marker])
90
83
 
@@ -8,44 +8,49 @@ module MarkdownIt
8
8
  DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i
9
9
  NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i
10
10
 
11
-
12
11
  #------------------------------------------------------------------------------
13
12
  def self.entity(state, silent)
14
13
  pos = state.pos
15
14
  max = state.posMax
16
15
 
17
- return false if charCodeAt(state.src, pos) != 0x26 # &
16
+ return false if charCodeAt(state.src, pos) != 0x26 # &
17
+
18
+ return false if pos + 1 >= max
18
19
 
19
- if pos + 1 < max
20
- ch = charCodeAt(state.src, pos + 1)
20
+ ch = charCodeAt(state.src, pos + 1)
21
21
 
22
- if ch == 0x23 # '#'
23
- match = state.src[pos..-1].match(DIGITAL_RE)
24
- if match
22
+ if ch == 0x23 # '#'
23
+ match = state.src[pos..-1].match(DIGITAL_RE)
24
+ if match
25
+ if !silent
26
+ code = match[1][0].downcase == 'x' ? match[1][1..-1].to_i(16) : match[1].to_i
27
+
28
+ token = state.push('text_special', '', 0)
29
+ token.content = isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD)
30
+ token.markup = match[0]
31
+ token.info = 'entity'
32
+ end
33
+ state.pos += match[0].length
34
+ return true
35
+ end
36
+ else
37
+ match = state.src[pos..-1].match(NAMED_RE)
38
+ if match
39
+ if MarkdownIt::HTMLEntities::MAPPINGS[match[1]]
25
40
  if !silent
26
- code = match[1][0].downcase == 'x' ? match[1][1..-1].to_i(16) : match[1].to_i
27
- state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD)
41
+ token = state.push('text_special', '', 0)
42
+ token.content += fromCodePoint(MarkdownIt::HTMLEntities::MAPPINGS[match[1]])
43
+ token.markup = match[0]
44
+ token.info = 'entity'
28
45
  end
29
46
  state.pos += match[0].length
30
47
  return true
31
48
  end
32
- else
33
- match = state.src[pos..-1].match(NAMED_RE)
34
- if match
35
- if MarkdownIt::HTMLEntities::MAPPINGS[match[1]]
36
- state.pending += fromCodePoint(MarkdownIt::HTMLEntities::MAPPINGS[match[1]]) if !silent
37
- state.pos += match[0].length
38
- return true
39
- end
40
- end
41
49
  end
42
50
  end
43
51
 
44
- state.pending += '&' if !silent
45
- state.pos += 1
46
- return true
52
+ return false
47
53
  end
48
-
49
54
  end
50
55
  end
51
56
  end
@@ -11,7 +11,6 @@ module MarkdownIt
11
11
 
12
12
  '\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-'.split('').each { |ch| ESCAPED[ch.ord] = 1 }
13
13
 
14
-
15
14
  #------------------------------------------------------------------------------
16
15
  def self.escape(state, silent)
17
16
  pos = state.pos
@@ -21,35 +20,55 @@ module MarkdownIt
21
20
 
22
21
  pos += 1
23
22
 
24
- if pos < max
25
- ch = charCodeAt(state.src, pos)
23
+ # '\' at the end of the inline block
24
+ return false if pos >= max
25
+
26
+ ch1 = charCodeAt(state.src, pos)
27
+
28
+ if ch1 == 0x0A
29
+ if !silent
30
+ state.push('hardbreak', 'br', 0)
31
+ end
26
32
 
27
- if ch < 256 && ESCAPED[ch] != 0
28
- state.pending += state.src[pos] if !silent
29
- state.pos += 2
30
- return true
33
+ pos += 1
34
+ # skip leading whitespaces from next line
35
+ while pos < max
36
+ ch1 = charCodeAt(state.src, pos)
37
+ break if !isSpace(ch1)
38
+ pos += 1
31
39
  end
32
40
 
33
- if ch == 0x0A
34
- if !silent
35
- state.push('hardbreak', 'br', 0)
36
- end
41
+ state.pos = pos
42
+ return true
43
+ end
44
+
45
+ escapedStr = state.src[pos]
37
46
 
47
+ if (ch1 >= 0xD800 && ch1 <= 0xDBFF && pos + 1 < max)
48
+ ch2 = charCodeAt(state.src, pos + 1)
49
+
50
+ if (ch2 >= 0xDC00 && ch2 <= 0xDFFF)
51
+ escapedStr += state.src[pos + 1]
38
52
  pos += 1
39
- # skip leading whitespaces from next line
40
- while pos < max
41
- ch = charCodeAt(state.src, pos)
42
- break if !isSpace(ch)
43
- pos += 1
44
- end
45
-
46
- state.pos = pos
47
- return true
48
53
  end
49
54
  end
50
55
 
51
- state.pending += '\\' if !silent
52
- state.pos += 1
56
+ origStr = '\\' + escapedStr
57
+
58
+ if (!silent)
59
+ token = state.push('text_special', '', 0)
60
+
61
+ if ch1 < 256 && ESCAPED[ch1] != 0
62
+ token.content = escapedStr
63
+ else
64
+ token.content = origStr
65
+ end
66
+
67
+ token.markup = origStr
68
+ token.info = 'escape'
69
+ end
70
+
71
+ state.pos = pos + 1
53
72
  return true
54
73
  end
55
74
  end
@@ -1,47 +1,48 @@
1
1
  # Clean up tokens after emphasis and strikethrough postprocessing:
2
- # Merge adjacent text nodes into one, and re-calculate all token levels
2
+ # merge adjacent text nodes into one and re-calculate all token levels
3
3
  #
4
4
  # This is necessary because initially emphasis delimiter markers (*, _, ~)
5
5
  # are treated as their own separate text tokens. Then emphasis rule either
6
6
  # leaves them as text (needed to merge with adjacent text) or turns them
7
7
  # into opening/closing tags (which messes up levels inside).
8
- #------------------------------------------------------------------------------
8
+ #
9
9
  module MarkdownIt
10
10
  module RulesInline
11
- class TextCollapse
11
+ class FragmentsJoin
12
+ extend Common::Utils
12
13
 
13
- #------------------------------------------------------------------------------
14
- def self.text_collapse(state)
15
- level = 0
16
- tokens = state.tokens
17
- max = state.tokens.length
14
+ def self.fragments_join(state)
15
+ level = 0
16
+ tokens = state.tokens
17
+ max = state.tokens.length
18
18
 
19
- last = curr = 0
19
+ last = 0
20
+ curr = 0
20
21
  while curr < max
21
22
  # re-calculate levels after emphasis/strikethrough turns some text nodes
22
23
  # into opening/closing tags
23
- level -= 1 if tokens[curr].nesting < 0 # closing tag
24
+ level -= 1 if (tokens[curr].nesting < 0) # closing tag
24
25
  tokens[curr].level = level
25
- level += 1 if tokens[curr].nesting > 0 # opening tag
26
-
27
- if tokens[curr].type == 'text' &&
26
+ level +=1 if (tokens[curr].nesting > 0) # opening tag
27
+
28
+ if (tokens[curr].type == 'text' &&
28
29
  curr + 1 < max &&
29
- tokens[curr + 1].type == 'text'
30
-
30
+ tokens[curr + 1].type == 'text')
31
+
31
32
  # collapse two adjacent text nodes
32
33
  tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content
33
34
  else
34
- tokens[last] = tokens[curr] if curr != last
35
-
35
+ tokens[last] = tokens[curr] if (curr != last)
36
+
36
37
  last += 1
37
38
  end
38
-
39
+
39
40
  curr += 1
40
41
  end
41
-
42
- if curr != last
43
- tokens.slice!(last..max)
44
- end
42
+
43
+ if (curr != last)
44
+ tokens.pop(tokens.length - last)
45
+ end
45
46
  end
46
47
  end
47
48
  end
@@ -6,6 +6,14 @@ module MarkdownIt
6
6
  extend Common::Utils
7
7
  include MarkdownIt::Common::HtmlRe
8
8
 
9
+ #------------------------------------------------------------------------------
10
+ def self.isLinkOpen(str)
11
+ return !(/^<a[>\s]/i =~ str).nil?
12
+ end
13
+ def self.isLinkClose(str)
14
+ return !(/^<\/a\s*>/i =~ str).nil?
15
+ end
16
+
9
17
  #------------------------------------------------------------------------------
10
18
  def self.isLetter(ch)
11
19
  lc = ch | 0x20 # to lower case
@@ -39,6 +47,9 @@ module MarkdownIt
39
47
  if !silent
40
48
  token = state.push('html_inline', '', 0)
41
49
  token.content = state.src.slice(pos...(pos + match[0].length))
50
+
51
+ state.linkLevel += 1 if (isLinkOpen(token.content))
52
+ state.linkLevel -= 1 if (isLinkClose(token.content))
42
53
  end
43
54
  state.pos += match[0].length
44
55
  return true
@@ -132,9 +132,11 @@ module MarkdownIt
132
132
  attrs.push([ 'title', title ])
133
133
  end
134
134
 
135
+ state.linkLevel += 1
135
136
  state.md.inline.tokenize(state)
137
+ state.linkLevel -= 1
136
138
 
137
- token = state.push('link_close', 'a', -1)
139
+ token = state.push('link_close', 'a', -1)
138
140
  end
139
141
 
140
142
  state.pos = pos
@@ -0,0 +1,60 @@
1
+ # Process links like https://example.org/
2
+ module MarkdownIt
3
+ module RulesInline
4
+ class Linkify
5
+ extend Common::Utils
6
+
7
+ # RFC3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
8
+ SCHEME_RE = /(?:^|[^a-z0-9.+-])([a-z][a-z0-9.+-]*)$/i
9
+
10
+ #------------------------------------------------------------------------------
11
+ def self.linkify(state, silent)
12
+ return false if (!state.md.options[:linkify])
13
+ return false if (state.linkLevel > 0)
14
+
15
+ pos = state.pos
16
+ max = state.posMax
17
+
18
+ return false if (pos + 3 > max)
19
+ return false if (charCodeAt(state.src, pos) != 0x3A) # :
20
+ return false if (charCodeAt(state.src, pos + 1) != 0x2F) # /
21
+ return false if (charCodeAt(state.src, pos + 2) != 0x2F) # /
22
+
23
+ match = state.pending.match(SCHEME_RE)
24
+ return false if (!match)
25
+
26
+ proto = match[1]
27
+
28
+ link = state.md.linkify.matchAtStart(state.src.slice((pos - proto.length)..-1))
29
+ return false if (!link)
30
+
31
+ url = link.url
32
+
33
+ # disallow '*' at the end of the link (conflicts with emphasis)
34
+ url = url.sub(/\*+$/, '')
35
+
36
+ fullUrl = state.md.normalizeLink.call(url)
37
+ return false if (!state.md.validateLink.call(fullUrl))
38
+
39
+ if (!silent)
40
+ state.pending = state.pending[0...-proto.length]
41
+
42
+ token = state.push('link_open', 'a', 1)
43
+ token.attrs = [ [ 'href', fullUrl ] ]
44
+ token.markup = 'linkify'
45
+ token.info = 'auto'
46
+
47
+ token = state.push('text', '', 0)
48
+ token.content = state.md.normalizeLinkText.call(url)
49
+
50
+ token = state.push('link_close', 'a', -1)
51
+ token.markup = 'linkify'
52
+ token.info = 'auto'
53
+ end
54
+
55
+ state.pos += url.length - proto.length
56
+ return true
57
+ end
58
+ end
59
+ end
60
+ end
@@ -20,7 +20,13 @@ module MarkdownIt
20
20
  if !silent
21
21
  if pmax >= 0 && charCodeAt(state.pending, pmax) == 0x20
22
22
  if pmax >= 1 && charCodeAt(state.pending, pmax - 1) == 0x20
23
- state.pending = state.pending.sub(/ +$/, '')
23
+ # Find whitespaces tail of pending chars.
24
+ ws = pmax - 1
25
+ while (ws >= 1 && charCodeAt(state.pending, ws - 1) == 0x20)
26
+ ws -= 1
27
+ end
28
+
29
+ state.pending = state.pending.slice(0...ws)
24
30
  state.push('hardbreak', 'br', 0)
25
31
  else
26
32
  state.pending = state.pending.slice(0...-1)
@@ -7,7 +7,7 @@ module MarkdownIt
7
7
 
8
8
  attr_accessor :src, :env, :md, :tokens, :pos, :posMax, :level, :tokens_meta
9
9
  attr_accessor :pending, :pendingLevel, :cache, :delimiters
10
- attr_accessor :backticks, :backticksScanned
10
+ attr_accessor :backticks, :backticksScanned, :linkLevel
11
11
 
12
12
  #------------------------------------------------------------------------------
13
13
  def initialize(src, md, env, outTokens)
@@ -36,6 +36,10 @@ module MarkdownIt
36
36
  # backtick length => last seen position
37
37
  @backticks = {}
38
38
  @backticksScanned = false
39
+
40
+ # Counter used to disable inline linkify-it execution
41
+ # inside <a> and markdown links
42
+ @linkLevel = 0
39
43
  end
40
44
 
41
45
 
@@ -35,7 +35,6 @@ module MarkdownIt
35
35
  state.delimiters.push({
36
36
  marker: marker,
37
37
  length: 0, # disable "rule of 3" length checks meant for emphasis
38
- jump: i / 2, # for `~~` 1 marker = 2 characters
39
38
  token: state.tokens.length - 1,
40
39
  end: -1,
41
40
  open: scanned[:can_open],
@@ -67,6 +67,7 @@ module MarkdownIt
67
67
  # *
68
68
  # * - Info string for "fence" tokens
69
69
  # * - The value "auto" for autolink "link_open" and "link_close" tokens
70
+ # * - The string value of the item marker for ordered-list "list_item_open" tokens
70
71
  @info = ''
71
72
 
72
73
  # * Token#meta -> Object
@@ -1,3 +1,3 @@
1
1
  module MotionMarkdownIt
2
- VERSION = '12.0.6'
2
+ VERSION = '13.0.1'
3
3
  end
@@ -40,6 +40,7 @@ else
40
40
  require 'motion-markdown-it/rules_core/replacements'
41
41
  require 'motion-markdown-it/rules_core/smartquotes'
42
42
  require 'motion-markdown-it/rules_core/state_core'
43
+ require 'motion-markdown-it/rules_core/text_join'
43
44
  require 'motion-markdown-it/rules_block/blockquote'
44
45
  require 'motion-markdown-it/rules_block/code'
45
46
  require 'motion-markdown-it/rules_block/fence'
@@ -58,17 +59,18 @@ else
58
59
  require 'motion-markdown-it/rules_inline/emphasis'
59
60
  require 'motion-markdown-it/rules_inline/entity'
60
61
  require 'motion-markdown-it/rules_inline/escape'
62
+ require 'motion-markdown-it/rules_inline/fragments_join'
61
63
  require 'motion-markdown-it/rules_inline/html_inline'
62
64
  require 'motion-markdown-it/rules_inline/image'
63
65
  require 'motion-markdown-it/rules_inline/link'
66
+ require 'motion-markdown-it/rules_inline/linkify'
64
67
  require 'motion-markdown-it/rules_inline/newline'
65
68
  require 'motion-markdown-it/rules_inline/state_inline'
66
69
  require 'motion-markdown-it/rules_inline/strikethrough'
67
- require 'motion-markdown-it/rules_inline/text_collapse'
68
70
  require 'motion-markdown-it/rules_inline/text'
69
71
 
70
72
  require 'motion-markdown-it/ruler'
71
73
  require 'motion-markdown-it/token'
72
74
  require 'motion-markdown-it/index'
73
75
 
74
- end
76
+ end
@@ -195,6 +195,9 @@ describe 'Misc' do
195
195
 
196
196
  expect(md.render('123')).to eq "<p>123</p>\n"
197
197
  expect(md.render("123\n")).to eq "<p>123</p>\n"
198
+
199
+ expect(md.render(' codeblock')).to eq "<pre><code>codeblock\n</code></pre>\n"
200
+ expect(md.render(" codeblock\n")).to eq "<pre><code>codeblock\n</code></pre>\n"
198
201
  end
199
202
 
200
203
  #------------------------------------------------------------------------------
@@ -273,6 +276,14 @@ describe 'Misc' do
273
276
  # );
274
277
  # end
275
278
 
279
+ # TODO ------------------------------------------------------------------------------
280
+ # it 'Should escape surrogate pairs (coverage)' do
281
+ # md = MarkdownIt::Parser.new({})
282
+ #
283
+ # expect(md.render("\\\uD835\uDC9C")).to eq "<p>\\\uD835\uDC9C</p>\n"
284
+ # expect(md.render("\\\uD835x")).to eq "<p>\\\uD835x</p>\n"
285
+ # expect(md.render("\\\uD835")).to eq "<p>\\\uD835</p>\n"
286
+ # end
276
287
  end
277
288
 
278
289
 
@@ -371,6 +382,42 @@ describe 'smartquotes' do
371
382
  end
372
383
  end
373
384
 
385
+ describe 'Ordered list info' do
386
+ md = MarkdownIt::Parser.new
387
+
388
+ def type_filter(tokens, type)
389
+ return tokens.select { |t| t.type === type }
390
+ end
391
+
392
+ it 'Should mark ordered list item tokens with info' do
393
+ tokens = md.parse("1. Foo\n2. Bar\n20. Fuzz", {})
394
+ expect(type_filter(tokens, 'ordered_list_open').length).to eq 1
395
+
396
+ tokens = type_filter(tokens, 'list_item_open')
397
+ expect(tokens.length).to eq 3
398
+ expect(tokens[0].info).to eq '1'
399
+ expect(tokens[0].markup).to eq '.'
400
+ expect(tokens[1].info).to eq '2'
401
+ expect(tokens[1].markup).to eq '.'
402
+ expect(tokens[2].info).to eq '20'
403
+ expect(tokens[2].markup).to eq '.'
404
+
405
+ tokens = md.parse(" 1. Foo\n2. Bar\n 20. Fuzz\n 199. Flp", {})
406
+ expect(type_filter(tokens, 'ordered_list_open').length).to eq 1
407
+
408
+ tokens = type_filter(tokens, 'list_item_open')
409
+ expect(tokens.length).to eq 4
410
+ expect(tokens[0].info).to eq '1'
411
+ expect(tokens[0].markup).to eq '.'
412
+ expect(tokens[1].info).to eq '2'
413
+ expect(tokens[1].markup).to eq '.'
414
+ expect(tokens[2].info).to eq '20'
415
+ expect(tokens[2].markup).to eq '.'
416
+ expect(tokens[3].info).to eq '199'
417
+ expect(tokens[3].markup).to eq '.'
418
+ end
419
+ end
420
+
374
421
  #------------------------------------------------------------------------------
375
422
  describe 'Token attributes' do
376
423
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-markdown-it
3
3
  version: !ruby/object:Gem::Version
4
- version: 12.0.6
4
+ version: 13.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Walker
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-01-17 00:00:00.000000000 Z
13
+ date: 2023-01-19 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: mdurl-rb
@@ -113,20 +113,22 @@ files:
113
113
  - lib/motion-markdown-it/rules_core/replacements.rb
114
114
  - lib/motion-markdown-it/rules_core/smartquotes.rb
115
115
  - lib/motion-markdown-it/rules_core/state_core.rb
116
+ - lib/motion-markdown-it/rules_core/text_join.rb
116
117
  - lib/motion-markdown-it/rules_inline/autolink.rb
117
118
  - lib/motion-markdown-it/rules_inline/backticks.rb
118
119
  - lib/motion-markdown-it/rules_inline/balance_pairs.rb
119
120
  - lib/motion-markdown-it/rules_inline/emphasis.rb
120
121
  - lib/motion-markdown-it/rules_inline/entity.rb
121
122
  - lib/motion-markdown-it/rules_inline/escape.rb
123
+ - lib/motion-markdown-it/rules_inline/fragments_join.rb
122
124
  - lib/motion-markdown-it/rules_inline/html_inline.rb
123
125
  - lib/motion-markdown-it/rules_inline/image.rb
124
126
  - lib/motion-markdown-it/rules_inline/link.rb
127
+ - lib/motion-markdown-it/rules_inline/linkify.rb
125
128
  - lib/motion-markdown-it/rules_inline/newline.rb
126
129
  - lib/motion-markdown-it/rules_inline/state_inline.rb
127
130
  - lib/motion-markdown-it/rules_inline/strikethrough.rb
128
131
  - lib/motion-markdown-it/rules_inline/text.rb
129
- - lib/motion-markdown-it/rules_inline/text_collapse.rb
130
132
  - lib/motion-markdown-it/token.rb
131
133
  - lib/motion-markdown-it/version.rb
132
134
  - spec/motion-markdown-it/commonmark_spec.rb