motion-markdown-it 12.0.6 → 13.0.1

Sign up to get free protection for your applications and to get access to all the features.
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