motion-markdown-it 4.4.0 → 8.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +69 -16
- data/lib/motion-markdown-it.rb +7 -5
- data/lib/motion-markdown-it/common/html_blocks.rb +6 -2
- data/lib/motion-markdown-it/common/utils.rb +19 -4
- data/lib/motion-markdown-it/helpers/helper_wrapper.rb +9 -0
- data/lib/motion-markdown-it/helpers/parse_link_destination.rb +8 -7
- data/lib/motion-markdown-it/index.rb +60 -18
- data/lib/motion-markdown-it/parser_block.rb +7 -10
- data/lib/motion-markdown-it/parser_inline.rb +50 -14
- data/lib/motion-markdown-it/presets/commonmark.rb +7 -1
- data/lib/motion-markdown-it/presets/default.rb +4 -3
- data/lib/motion-markdown-it/presets/zero.rb +6 -1
- data/lib/motion-markdown-it/renderer.rb +46 -14
- data/lib/motion-markdown-it/rules_block/blockquote.rb +167 -31
- data/lib/motion-markdown-it/rules_block/code.rb +4 -3
- data/lib/motion-markdown-it/rules_block/fence.rb +9 -4
- data/lib/motion-markdown-it/rules_block/heading.rb +8 -3
- data/lib/motion-markdown-it/rules_block/hr.rb +10 -5
- data/lib/motion-markdown-it/rules_block/html_block.rb +6 -3
- data/lib/motion-markdown-it/rules_block/lheading.rb +64 -26
- data/lib/motion-markdown-it/rules_block/list.rb +91 -22
- data/lib/motion-markdown-it/rules_block/paragraph.rb +14 -9
- data/lib/motion-markdown-it/rules_block/reference.rb +24 -14
- data/lib/motion-markdown-it/rules_block/state_block.rb +79 -24
- data/lib/motion-markdown-it/rules_block/table.rb +52 -26
- data/lib/motion-markdown-it/rules_core/normalize.rb +1 -23
- data/lib/motion-markdown-it/rules_core/replacements.rb +22 -2
- data/lib/motion-markdown-it/rules_core/smartquotes.rb +41 -12
- data/lib/motion-markdown-it/rules_inline/autolink.rb +5 -4
- data/lib/motion-markdown-it/rules_inline/balance_pairs.rb +48 -0
- data/lib/motion-markdown-it/rules_inline/emphasis.rb +104 -149
- data/lib/motion-markdown-it/rules_inline/entity.rb +2 -2
- data/lib/motion-markdown-it/rules_inline/escape.rb +5 -3
- data/lib/motion-markdown-it/rules_inline/image.rb +12 -23
- data/lib/motion-markdown-it/rules_inline/link.rb +20 -25
- data/lib/motion-markdown-it/rules_inline/newline.rb +2 -1
- data/lib/motion-markdown-it/rules_inline/state_inline.rb +60 -1
- data/lib/motion-markdown-it/rules_inline/strikethrough.rb +81 -97
- data/lib/motion-markdown-it/rules_inline/text_collapse.rb +40 -0
- data/lib/motion-markdown-it/token.rb +46 -1
- data/lib/motion-markdown-it/version.rb +1 -1
- data/spec/motion-markdown-it/markdown_it_spec.rb +2 -2
- data/spec/motion-markdown-it/misc_spec.rb +90 -14
- data/spec/motion-markdown-it/testgen_helper.rb +1 -1
- data/spec/spec_helper.rb +2 -3
- metadata +13 -13
- data/lib/motion-markdown-it/common/url_schemas.rb +0 -173
- data/spec/motion-markdown-it/bench_mark_spec.rb +0 -44
@@ -7,20 +7,20 @@ module MarkdownIt
|
|
7
7
|
class ParserBlock
|
8
8
|
|
9
9
|
attr_accessor :ruler
|
10
|
-
|
10
|
+
|
11
11
|
RULES = [
|
12
12
|
# First 2 params - rule name & source. Secondary array - list of rules,
|
13
13
|
# which can be terminated by this one.
|
14
|
+
[ 'table', lambda { |state, startLine, endLine, silent| RulesBlock::Table.table(state, startLine, endLine, silent) }, [ 'paragraph', 'reference' ] ],
|
14
15
|
[ 'code', lambda { |state, startLine, endLine, silent| RulesBlock::Code.code(state, startLine, endLine, silent) } ],
|
15
16
|
[ 'fence', lambda { |state, startLine, endLine, silent| RulesBlock::Fence.fence(state, startLine, endLine, silent) }, [ 'paragraph', 'reference', 'blockquote', 'list' ] ],
|
16
|
-
[ 'blockquote', lambda { |state, startLine, endLine, silent| RulesBlock::Blockquote.blockquote(state, startLine, endLine, silent) }, [ 'paragraph', 'reference', 'list' ] ],
|
17
|
+
[ 'blockquote', lambda { |state, startLine, endLine, silent| RulesBlock::Blockquote.blockquote(state, startLine, endLine, silent) }, [ 'paragraph', 'reference', 'blockquote', 'list' ] ],
|
17
18
|
[ 'hr', lambda { |state, startLine, endLine, silent| RulesBlock::Hr.hr(state, startLine, endLine, silent) }, [ 'paragraph', 'reference', 'blockquote', 'list' ] ],
|
18
19
|
[ 'list', lambda { |state, startLine, endLine, silent| RulesBlock::List.list(state, startLine, endLine, silent) }, [ 'paragraph', 'reference', 'blockquote' ] ],
|
19
20
|
[ 'reference', lambda { |state, startLine, endLine, silent| RulesBlock::Reference.reference(state, startLine, endLine, silent) } ],
|
20
21
|
[ 'heading', lambda { |state, startLine, endLine, silent| RulesBlock::Heading.heading(state, startLine, endLine, silent) }, [ 'paragraph', 'reference', 'blockquote' ] ],
|
21
22
|
[ 'lheading', lambda { |state, startLine, endLine, silent| RulesBlock::Lheading.lheading(state, startLine, endLine, silent) } ],
|
22
23
|
[ 'html_block', lambda { |state, startLine, endLine, silent| RulesBlock::HtmlBlock.html_block(state, startLine, endLine, silent) }, [ 'paragraph', 'reference', 'blockquote' ] ],
|
23
|
-
[ 'table', lambda { |state, startLine, endLine, silent| RulesBlock::Table.table(state, startLine, endLine, silent) }, [ 'paragraph', 'reference' ] ],
|
24
24
|
[ 'paragraph', lambda { |state, startLine, endLine, silent| RulesBlock::Paragraph.paragraph(state, startLine) } ]
|
25
25
|
]
|
26
26
|
|
@@ -47,14 +47,14 @@ module MarkdownIt
|
|
47
47
|
line = startLine
|
48
48
|
hasEmptyLines = false
|
49
49
|
maxNesting = state.md.options[:maxNesting]
|
50
|
-
|
50
|
+
|
51
51
|
while line < endLine
|
52
52
|
state.line = line = state.skipEmptyLines(line)
|
53
53
|
break if line >= endLine
|
54
54
|
|
55
55
|
# Termination condition for nested calls.
|
56
56
|
# Nested calls currently used for blockquotes & lists
|
57
|
-
break if state.
|
57
|
+
break if state.sCount[line] < state.blkIndent
|
58
58
|
|
59
59
|
# If nesting level exceeded - skip tail to the end. That's not ordinary
|
60
60
|
# situation and we should not care about content.
|
@@ -74,7 +74,7 @@ module MarkdownIt
|
|
74
74
|
break if ok
|
75
75
|
end
|
76
76
|
|
77
|
-
# set state.tight
|
77
|
+
# set state.tight if we had an empty line before current tag
|
78
78
|
# i.e. latest empty line should not count
|
79
79
|
state.tight = !hasEmptyLines
|
80
80
|
|
@@ -88,9 +88,6 @@ module MarkdownIt
|
|
88
88
|
if line < endLine && state.isEmpty(line)
|
89
89
|
hasEmptyLines = true
|
90
90
|
line += 1
|
91
|
-
|
92
|
-
# two empty lines should stop the parser in list mode
|
93
|
-
break if line < endLine && state.parentType == 'list' && state.isEmpty(line)
|
94
91
|
state.line = line
|
95
92
|
end
|
96
93
|
end
|
@@ -102,7 +99,7 @@ module MarkdownIt
|
|
102
99
|
#------------------------------------------------------------------------------
|
103
100
|
def parse(src, md, env, outTokens)
|
104
101
|
|
105
|
-
|
102
|
+
return if !src
|
106
103
|
|
107
104
|
state = RulesBlock::StateBlock.new(src, md, env, outTokens)
|
108
105
|
|
@@ -5,9 +5,9 @@
|
|
5
5
|
#------------------------------------------------------------------------------
|
6
6
|
module MarkdownIt
|
7
7
|
class ParserInline
|
8
|
-
|
9
|
-
attr_accessor :ruler
|
10
|
-
|
8
|
+
|
9
|
+
attr_accessor :ruler, :ruler2
|
10
|
+
|
11
11
|
#------------------------------------------------------------------------------
|
12
12
|
# Parser rules
|
13
13
|
|
@@ -16,8 +16,8 @@ module MarkdownIt
|
|
16
16
|
[ 'newline', lambda { |state, startLine| RulesInline::Newline.newline(state, startLine) } ],
|
17
17
|
[ 'escape', lambda { |state, startLine| RulesInline::Escape.escape(state, startLine) } ],
|
18
18
|
[ 'backticks', lambda { |state, startLine| RulesInline::Backticks.backtick(state, startLine) } ],
|
19
|
-
[ 'strikethrough', lambda { |state,
|
20
|
-
[ 'emphasis', lambda { |state,
|
19
|
+
[ 'strikethrough', lambda { |state, silent| RulesInline::Strikethrough.tokenize(state, silent) } ],
|
20
|
+
[ 'emphasis', lambda { |state, silent| RulesInline::Emphasis.tokenize(state, silent) } ],
|
21
21
|
[ 'link', lambda { |state, startLine| RulesInline::Link.link(state, startLine) } ],
|
22
22
|
[ 'image', lambda { |state, startLine| RulesInline::Image.image(state, startLine) } ],
|
23
23
|
[ 'autolink', lambda { |state, startLine| RulesInline::Autolink.autolink(state, startLine) } ],
|
@@ -25,6 +25,12 @@ module MarkdownIt
|
|
25
25
|
[ 'entity', lambda { |state, startLine| RulesInline::Entity.entity(state, startLine) } ],
|
26
26
|
]
|
27
27
|
|
28
|
+
RULES2 = [
|
29
|
+
[ 'balance_pairs', lambda { |state| RulesInline::BalancePairs.link_pairs(state) } ],
|
30
|
+
[ 'strikethrough', lambda { |state| RulesInline::Strikethrough.postProcess(state) } ],
|
31
|
+
[ 'emphasis', lambda { |state| RulesInline::Emphasis.postProcess(state) } ],
|
32
|
+
[ 'text_collapse', lambda { |state| RulesInline::TextCollapse.text_collapse(state) } ]
|
33
|
+
];
|
28
34
|
|
29
35
|
#------------------------------------------------------------------------------
|
30
36
|
def initialize
|
@@ -36,6 +42,16 @@ module MarkdownIt
|
|
36
42
|
RULES.each do |rule|
|
37
43
|
@ruler.push(rule[0], rule[1])
|
38
44
|
end
|
45
|
+
|
46
|
+
# ParserInline#ruler2 -> Ruler
|
47
|
+
#
|
48
|
+
# [[Ruler]] instance. Second ruler used for post-processing
|
49
|
+
# (e.g. in emphasis-like rules).
|
50
|
+
@ruler2 = Ruler.new
|
51
|
+
|
52
|
+
RULES2.each do |rule|
|
53
|
+
@ruler2.push(rule[0], rule[1])
|
54
|
+
end
|
39
55
|
end
|
40
56
|
|
41
57
|
# Skip single token by running all rules in validation mode;
|
@@ -47,28 +63,42 @@ module MarkdownIt
|
|
47
63
|
len = rules.length
|
48
64
|
maxNesting = state.md.options[:maxNesting]
|
49
65
|
cache = state.cache
|
50
|
-
|
66
|
+
ok = false
|
51
67
|
|
52
68
|
if cache[pos] != nil
|
53
69
|
state.pos = cache[pos]
|
54
70
|
return
|
55
71
|
end
|
56
72
|
|
57
|
-
# istanbul ignore else
|
58
73
|
if state.level < maxNesting
|
59
74
|
0.upto(len -1) do |i|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
75
|
+
# Increment state.level and decrement it later to limit recursion.
|
76
|
+
# It's harmless to do here, because no tokens are created. But ideally,
|
77
|
+
# we'd need a separate private state variable for this purpose.
|
78
|
+
state.level += 1
|
79
|
+
ok = rules[i].call(state, true)
|
80
|
+
state.level -= 1
|
81
|
+
|
82
|
+
break if ok
|
64
83
|
end
|
84
|
+
else
|
85
|
+
# Too much nesting, just skip until the end of the paragraph.
|
86
|
+
#
|
87
|
+
# NOTE: this will cause links to behave incorrectly in the following case,
|
88
|
+
# when an amount of `[` is exactly equal to `maxNesting + 1`:
|
89
|
+
#
|
90
|
+
# [[[[[[[[[[[[[[[[[[[[[foo]()
|
91
|
+
#
|
92
|
+
# TODO: remove this workaround when CM standard will allow nested links
|
93
|
+
# (we can replace it by preventing links from being parsed in
|
94
|
+
# validation mode)
|
95
|
+
state.pos = state.posMax
|
65
96
|
end
|
66
97
|
|
67
|
-
state.pos += 1
|
98
|
+
state.pos += 1 if !ok
|
68
99
|
cache[pos] = state.pos
|
69
100
|
end
|
70
101
|
|
71
|
-
|
72
102
|
# Generate tokens for input range
|
73
103
|
#------------------------------------------------------------------------------
|
74
104
|
def tokenize(state)
|
@@ -115,7 +145,13 @@ module MarkdownIt
|
|
115
145
|
state = RulesInline::StateInline.new(str, md, env, outTokens)
|
116
146
|
|
117
147
|
tokenize(state)
|
118
|
-
end
|
119
148
|
|
149
|
+
rules = @ruler2.getRules('')
|
150
|
+
len = rules.length
|
151
|
+
|
152
|
+
0.upto(len - 1) do |i|
|
153
|
+
rules[i].call(state)
|
154
|
+
end
|
155
|
+
end
|
120
156
|
end
|
121
157
|
end
|
@@ -23,7 +23,8 @@ module MarkdownIt
|
|
23
23
|
quotes: "\u201c\u201d\u2018\u2019", # “”‘’
|
24
24
|
|
25
25
|
# Highlighter function. Should return escaped HTML,
|
26
|
-
# or '' if
|
26
|
+
# or '' if the source string is not changed and should be escaped externaly.
|
27
|
+
# If result starts with <pre... internal wrapper is skipped.
|
27
28
|
#
|
28
29
|
# function (/*str, lang*/) { return ''; }
|
29
30
|
#
|
@@ -69,6 +70,11 @@ module MarkdownIt
|
|
69
70
|
'link',
|
70
71
|
'newline',
|
71
72
|
'text'
|
73
|
+
],
|
74
|
+
rules2: [
|
75
|
+
'balance_pairs',
|
76
|
+
'emphasis',
|
77
|
+
'text_collapse'
|
72
78
|
]
|
73
79
|
}
|
74
80
|
}
|
@@ -20,16 +20,17 @@ module MarkdownIt
|
|
20
20
|
#
|
21
21
|
# For example, you can use '«»„“' for Russian, '„“‚‘' for German,
|
22
22
|
# and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
|
23
|
-
quotes: "\u201c\u201d\u2018\u2019", # “”‘’
|
23
|
+
quotes: "\u201c\u201d\u2018\u2019", # “”‘’
|
24
24
|
|
25
25
|
# Highlighter function. Should return escaped HTML,
|
26
|
-
# or '' if
|
26
|
+
# or '' if the source string is not changed and should be escaped externaly.
|
27
|
+
# If result starts with <pre... internal wrapper is skipped.
|
27
28
|
#
|
28
29
|
# function (/*str, lang*/) { return ''; }
|
29
30
|
#
|
30
31
|
highlight: nil,
|
31
32
|
|
32
|
-
maxNesting:
|
33
|
+
maxNesting: 100 # Internal protection, recursion limit
|
33
34
|
},
|
34
35
|
|
35
36
|
components: {
|
@@ -24,7 +24,8 @@ module MarkdownIt
|
|
24
24
|
quotes: "\u201c\u201d\u2018\u2019", # “”‘’
|
25
25
|
|
26
26
|
# Highlighter function. Should return escaped HTML,
|
27
|
-
# or '' if
|
27
|
+
# or '' if the source string is not changed and should be escaped externaly.
|
28
|
+
# If result starts with <pre... internal wrapper is skipped.
|
28
29
|
#
|
29
30
|
# function (/*str, lang*/) { return ''; }
|
30
31
|
#
|
@@ -52,6 +53,10 @@ module MarkdownIt
|
|
52
53
|
inline: {
|
53
54
|
rules: [
|
54
55
|
'text'
|
56
|
+
],
|
57
|
+
rules2: [
|
58
|
+
'balance_pairs',
|
59
|
+
'text_collapse'
|
55
60
|
]
|
56
61
|
}
|
57
62
|
}
|
@@ -10,16 +10,24 @@ module MarkdownIt
|
|
10
10
|
extend MarkdownIt::Common::Utils
|
11
11
|
|
12
12
|
attr_accessor :rules
|
13
|
-
|
13
|
+
|
14
14
|
# Default Rules
|
15
15
|
#------------------------------------------------------------------------------
|
16
|
-
def self.code_inline(tokens, idx)
|
17
|
-
|
16
|
+
def self.code_inline(tokens, idx, options, env, renderer)
|
17
|
+
token = tokens[idx]
|
18
|
+
|
19
|
+
return '<code' + renderer.renderAttrs(token) + '>' +
|
20
|
+
escapeHtml(tokens[idx].content) +
|
21
|
+
'</code>'
|
18
22
|
end
|
19
23
|
|
20
24
|
#------------------------------------------------------------------------------
|
21
|
-
def self.code_block(tokens, idx)
|
22
|
-
|
25
|
+
def self.code_block(tokens, idx, options, env, renderer)
|
26
|
+
token = tokens[idx]
|
27
|
+
|
28
|
+
return '<pre' + renderer.renderAttrs(token) + '><code>' +
|
29
|
+
escapeHtml(tokens[idx].content) +
|
30
|
+
"</code></pre>\n"
|
23
31
|
end
|
24
32
|
|
25
33
|
#------------------------------------------------------------------------------
|
@@ -30,7 +38,6 @@ module MarkdownIt
|
|
30
38
|
|
31
39
|
if !info.empty?
|
32
40
|
langName = info.split(/\s+/)[0]
|
33
|
-
token.attrPush([ 'class', options[:langPrefix] + langName ])
|
34
41
|
end
|
35
42
|
|
36
43
|
if options[:highlight]
|
@@ -39,7 +46,33 @@ module MarkdownIt
|
|
39
46
|
highlighted = escapeHtml(token.content)
|
40
47
|
end
|
41
48
|
|
42
|
-
|
49
|
+
if highlighted.start_with?('<pre')
|
50
|
+
return highlighted + "\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
# If language exists, inject class gently, without modifying original token.
|
54
|
+
# May be, one day we will add .clone() for token and simplify this part, but
|
55
|
+
# now we prefer to keep things local.
|
56
|
+
if !info.empty?
|
57
|
+
i = token.attrIndex('class')
|
58
|
+
tmpAttrs = token.attrs ? token.attrs.dup : []
|
59
|
+
|
60
|
+
if i < 0
|
61
|
+
tmpAttrs.push([ 'class', options[:langPrefix] + langName ])
|
62
|
+
else
|
63
|
+
tmpAttrs[i][1] += ' ' + options[:langPrefix] + langName
|
64
|
+
end
|
65
|
+
|
66
|
+
# Fake token just to render attributes
|
67
|
+
tmpToken = Token.new(nil, nil, nil)
|
68
|
+
tmpToken.attrs = tmpAttrs
|
69
|
+
|
70
|
+
return '<pre><code' + renderer.renderAttrs(tmpToken) + '>' +
|
71
|
+
highlighted +
|
72
|
+
"</code></pre>\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
return '<pre><code' + renderer.renderAttrs(token) + '>' + highlighted + "</code></pre>\n"
|
43
76
|
end
|
44
77
|
|
45
78
|
#------------------------------------------------------------------------------
|
@@ -84,8 +117,8 @@ module MarkdownIt
|
|
84
117
|
#------------------------------------------------------------------------------
|
85
118
|
def initialize
|
86
119
|
@default_rules = {
|
87
|
-
'code_inline' => lambda {|tokens, idx, options, env, renderer| Renderer.code_inline(tokens, idx)},
|
88
|
-
'code_block' => lambda {|tokens, idx, options, env, renderer| Renderer.code_block(tokens, idx)},
|
120
|
+
'code_inline' => lambda {|tokens, idx, options, env, renderer| Renderer.code_inline(tokens, idx, options, env, renderer)},
|
121
|
+
'code_block' => lambda {|tokens, idx, options, env, renderer| Renderer.code_block(tokens, idx, options, env, renderer)},
|
89
122
|
'fence' => lambda {|tokens, idx, options, env, renderer| Renderer.fence(tokens, idx, options, env, renderer)},
|
90
123
|
'image' => lambda {|tokens, idx, options, env, renderer| Renderer.image(tokens, idx, options, env, renderer)},
|
91
124
|
'hardbreak' => lambda {|tokens, idx, options, env, renderer| Renderer.hardbreak(tokens, idx, options)},
|
@@ -94,7 +127,7 @@ module MarkdownIt
|
|
94
127
|
'html_block' => lambda {|tokens, idx, options, env, renderer| Renderer.html_block(tokens, idx)},
|
95
128
|
'html_inline' => lambda {|tokens, idx, options, env, renderer| Renderer.html_inline(tokens, idx)}
|
96
129
|
}
|
97
|
-
|
130
|
+
|
98
131
|
# Renderer#rules -> Object
|
99
132
|
#
|
100
133
|
# Contains render rules for tokens. Can be updated and extended.
|
@@ -110,7 +143,7 @@ module MarkdownIt
|
|
110
143
|
# var result = md.renderInline(...);
|
111
144
|
# ```
|
112
145
|
#
|
113
|
-
# Each rule is called as
|
146
|
+
# Each rule is called as independet static function with fixed signature:
|
114
147
|
#
|
115
148
|
# ```javascript
|
116
149
|
# function my_token_render(tokens, idx, options, env, renderer) {
|
@@ -220,7 +253,7 @@ module MarkdownIt
|
|
220
253
|
|
221
254
|
0.upto(tokens.length - 1) do |i|
|
222
255
|
type = tokens[i].type
|
223
|
-
|
256
|
+
|
224
257
|
if rules[type] != nil
|
225
258
|
result += rules[type].call(tokens, i, options, env, self)
|
226
259
|
else
|
@@ -244,11 +277,10 @@ module MarkdownIt
|
|
244
277
|
#------------------------------------------------------------------------------
|
245
278
|
def renderInlineAsText(tokens, options, env)
|
246
279
|
result = ''
|
247
|
-
rules = @rules
|
248
280
|
|
249
281
|
0.upto(tokens.length - 1) do |i|
|
250
282
|
if tokens[i].type == 'text'
|
251
|
-
result +=
|
283
|
+
result += tokens[i].content
|
252
284
|
elsif tokens[i].type == 'image'
|
253
285
|
result += renderInlineAsText(tokens[i].children, options, env)
|
254
286
|
end
|
@@ -3,38 +3,93 @@
|
|
3
3
|
module MarkdownIt
|
4
4
|
module RulesBlock
|
5
5
|
class Blockquote
|
6
|
+
extend Common::Utils
|
6
7
|
|
7
8
|
#------------------------------------------------------------------------------
|
8
9
|
def self.blockquote(state, startLine, endLine, silent)
|
9
|
-
|
10
|
-
|
10
|
+
oldLineMax = state.lineMax
|
11
|
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
12
|
+
max = state.eMarks[startLine]
|
13
|
+
|
14
|
+
# if it's indented more than 3 spaces, it should be a code block
|
15
|
+
return false if (state.sCount[startLine] - state.blkIndent >= 4)
|
11
16
|
|
12
17
|
# check the block quote marker
|
13
18
|
return false if state.src.charCodeAt(pos) != 0x3E # >
|
14
19
|
pos += 1
|
15
|
-
|
20
|
+
|
16
21
|
# we know that it's going to be a valid blockquote,
|
17
22
|
# so no point trying to find the end of it in silent mode
|
18
23
|
return true if silent
|
19
24
|
|
25
|
+
# skip spaces after ">" and re-calculate offset
|
26
|
+
initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine])
|
27
|
+
|
20
28
|
# skip one optional space after '>'
|
21
|
-
|
29
|
+
if state.src.charCodeAt(pos) == 0x20 # space
|
30
|
+
# ' > test '
|
31
|
+
# ^ -- position start of line here:
|
32
|
+
pos += 1
|
33
|
+
initial += 1
|
34
|
+
offset +=1
|
35
|
+
adjustTab = false
|
36
|
+
spaceAfterMarker = true
|
37
|
+
elsif state.src.charCodeAt(pos) == 0x09 # tab
|
38
|
+
spaceAfterMarker = true
|
22
39
|
|
23
|
-
|
24
|
-
|
40
|
+
if ((state.bsCount[startLine] + offset) % 4 == 3)
|
41
|
+
# ' >\t test '
|
42
|
+
# ^ -- position start of line here (tab has width===1)
|
43
|
+
pos += 1
|
44
|
+
initial += 1
|
45
|
+
offset += 1
|
46
|
+
adjustTab = false
|
47
|
+
else
|
48
|
+
# ' >\t test '
|
49
|
+
# ^ -- position start of line here + shift bsCount slightly
|
50
|
+
# to make extra space appear
|
51
|
+
adjustTab = true
|
52
|
+
end
|
53
|
+
else
|
54
|
+
spaceAfterMarker = false
|
55
|
+
end
|
25
56
|
|
26
57
|
oldBMarks = [ state.bMarks[startLine] ]
|
27
58
|
state.bMarks[startLine] = pos
|
28
59
|
|
29
|
-
|
30
|
-
|
31
|
-
|
60
|
+
while pos < max
|
61
|
+
ch = state.src.charCodeAt(pos)
|
62
|
+
|
63
|
+
if isSpace(ch)
|
64
|
+
if ch == 0x09
|
65
|
+
offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4
|
66
|
+
else
|
67
|
+
offset += 1
|
68
|
+
end
|
69
|
+
else
|
70
|
+
break
|
71
|
+
end
|
72
|
+
|
73
|
+
pos += 1
|
74
|
+
end
|
75
|
+
|
76
|
+
oldBSCount = [ state.bsCount[startLine] ]
|
77
|
+
state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0)
|
78
|
+
|
79
|
+
lastLineEmpty = pos >= max
|
80
|
+
|
81
|
+
oldSCount = [ state.sCount[startLine] ]
|
82
|
+
state.sCount[startLine] = offset - initial
|
32
83
|
|
33
84
|
oldTShift = [ state.tShift[startLine] ]
|
34
85
|
state.tShift[startLine] = pos - state.bMarks[startLine]
|
35
86
|
|
36
87
|
terminatorRules = state.md.block.ruler.getRules('blockquote')
|
37
88
|
|
89
|
+
oldParentType = state.parentType
|
90
|
+
state.parentType = 'blockquote'
|
91
|
+
wasOutdented = false
|
92
|
+
|
38
93
|
# Search the end of the block
|
39
94
|
#
|
40
95
|
# Block ends with either:
|
@@ -48,15 +103,23 @@ module MarkdownIt
|
|
48
103
|
# >
|
49
104
|
# test
|
50
105
|
# ```
|
51
|
-
# 3. another tag
|
106
|
+
# 3. another tag:
|
52
107
|
# ```
|
53
108
|
# > test
|
54
109
|
# - - -
|
55
110
|
# ```
|
56
111
|
nextLine = startLine + 1
|
57
112
|
while nextLine < endLine
|
58
|
-
|
59
|
-
|
113
|
+
# check if it's outdented, i.e. it's inside list item and indented
|
114
|
+
# less than said list item:
|
115
|
+
#
|
116
|
+
# ```
|
117
|
+
# 1. anything
|
118
|
+
# > current blockquote
|
119
|
+
# 2. checking this line
|
120
|
+
# ```
|
121
|
+
wasOutdented = true if (state.sCount[nextLine] < state.blkIndent)
|
122
|
+
|
60
123
|
pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
61
124
|
max = state.eMarks[nextLine]
|
62
125
|
|
@@ -65,19 +128,69 @@ module MarkdownIt
|
|
65
128
|
break
|
66
129
|
end
|
67
130
|
|
68
|
-
if state.src.charCodeAt(pos) == 0x3E
|
131
|
+
if state.src.charCodeAt(pos) == 0x3E && !wasOutdented # >
|
69
132
|
pos += 1
|
70
133
|
# This line is inside the blockquote.
|
71
134
|
|
135
|
+
# skip spaces after ">" and re-calculate offset
|
136
|
+
initial = offset = state.sCount[nextLine] + pos - (state.bMarks[nextLine] + state.tShift[nextLine])
|
137
|
+
|
72
138
|
# skip one optional space after '>'
|
73
|
-
|
139
|
+
if state.src.charCodeAt(pos) == 0x20 # space
|
140
|
+
# ' > test '
|
141
|
+
# ^ -- position start of line here:
|
142
|
+
pos += 1
|
143
|
+
initial += 1
|
144
|
+
offset += 1
|
145
|
+
adjustTab = false
|
146
|
+
spaceAfterMarker = true
|
147
|
+
elsif state.src.charCodeAt(pos) == 0x09 # tab
|
148
|
+
spaceAfterMarker = true
|
149
|
+
|
150
|
+
if ((state.bsCount[nextLine] + offset) % 4 == 3)
|
151
|
+
# ' >\t test '
|
152
|
+
# ^ -- position start of line here (tab has width===1)
|
153
|
+
pos += 1
|
154
|
+
initial += 1
|
155
|
+
offset += 1
|
156
|
+
adjustTab = false
|
157
|
+
else
|
158
|
+
# ' >\t test '
|
159
|
+
# ^ -- position start of line here + shift bsCount slightly
|
160
|
+
# to make extra space appear
|
161
|
+
adjustTab = true
|
162
|
+
end
|
163
|
+
else
|
164
|
+
spaceAfterMarker = false
|
165
|
+
end
|
74
166
|
|
75
167
|
oldBMarks.push(state.bMarks[nextLine])
|
76
168
|
state.bMarks[nextLine] = pos
|
77
169
|
|
78
|
-
|
170
|
+
while pos < max
|
171
|
+
ch = state.src.charCodeAt(pos)
|
172
|
+
|
173
|
+
if isSpace(ch)
|
174
|
+
if ch == 0x09
|
175
|
+
offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4
|
176
|
+
else
|
177
|
+
offset += 1
|
178
|
+
end
|
179
|
+
else
|
180
|
+
break
|
181
|
+
end
|
182
|
+
|
183
|
+
pos += 1
|
184
|
+
end
|
185
|
+
|
79
186
|
lastLineEmpty = pos >= max
|
80
187
|
|
188
|
+
oldBSCount.push(state.bsCount[nextLine])
|
189
|
+
state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0)
|
190
|
+
|
191
|
+
oldSCount.push(state.sCount[nextLine])
|
192
|
+
state.sCount[nextLine] = offset - initial\
|
193
|
+
|
81
194
|
oldTShift.push(state.tShift[nextLine])
|
82
195
|
state.tShift[nextLine] = pos - state.bMarks[nextLine]
|
83
196
|
nextLine += 1
|
@@ -97,39 +210,62 @@ module MarkdownIt
|
|
97
210
|
break
|
98
211
|
end
|
99
212
|
end
|
100
|
-
|
213
|
+
|
214
|
+
if terminate
|
215
|
+
# Quirk to enforce "hard termination mode" for paragraphs;
|
216
|
+
# normally if you call `tokenize(state, startLine, nextLine)`,
|
217
|
+
# paragraphs will look below nextLine for paragraph continuation,
|
218
|
+
# but if blockquote is terminated by another tag, they shouldn't
|
219
|
+
state.lineMax = nextLine
|
220
|
+
|
221
|
+
if state.blkIndent != 0
|
222
|
+
# state.blkIndent was non-zero, we now set it to zero,
|
223
|
+
# so we need to re-calculate all offsets to appear as
|
224
|
+
# if indent wasn't changed
|
225
|
+
oldBMarks.push(state.bMarks[nextLine])
|
226
|
+
oldBSCount.push(state.bsCount[nextLine])
|
227
|
+
oldTShift.push(state.tShift[nextLine])
|
228
|
+
oldSCount.push(state.sCount[nextLine])
|
229
|
+
state.sCount[nextLine] -= state.blkIndent
|
230
|
+
end
|
231
|
+
|
232
|
+
break
|
233
|
+
end
|
101
234
|
|
102
235
|
oldBMarks.push(state.bMarks[nextLine])
|
236
|
+
oldBSCount.push(state.bsCount[nextLine])
|
103
237
|
oldTShift.push(state.tShift[nextLine])
|
238
|
+
oldSCount.push(state.sCount[nextLine])
|
104
239
|
|
105
|
-
# A negative
|
240
|
+
# A negative indentation means that this is a paragraph continuation
|
106
241
|
#
|
107
|
-
|
108
|
-
# to be large enough to make any bugs obvious.
|
109
|
-
state.tShift[nextLine] = -1
|
242
|
+
state.sCount[nextLine] = -1
|
110
243
|
nextLine += 1
|
111
244
|
end
|
112
245
|
|
113
|
-
|
114
|
-
state.
|
246
|
+
oldIndent = state.blkIndent
|
247
|
+
state.blkIndent = 0
|
115
248
|
|
116
|
-
token
|
117
|
-
token.markup
|
118
|
-
token.map
|
249
|
+
token = state.push('blockquote_open', 'blockquote', 1)
|
250
|
+
token.markup = '>'
|
251
|
+
token.map = lines = [ startLine, 0 ]
|
119
252
|
|
120
253
|
state.md.block.tokenize(state, startLine, nextLine)
|
121
254
|
|
122
|
-
token
|
123
|
-
token.markup
|
255
|
+
token = state.push('blockquote_close', 'blockquote', -1)
|
256
|
+
token.markup = '>'
|
124
257
|
|
125
|
-
state.
|
126
|
-
|
258
|
+
state.lineMax = oldLineMax;
|
259
|
+
state.parentType = oldParentType
|
260
|
+
lines[1] = state.line
|
127
261
|
|
128
262
|
# Restore original tShift; this might not be necessary since the parser
|
129
263
|
# has already been here, but just to make sure we can do that.
|
130
264
|
(0...oldTShift.length).each do |i|
|
131
|
-
state.bMarks[i + startLine]
|
132
|
-
state.tShift[i + startLine]
|
265
|
+
state.bMarks[i + startLine] = oldBMarks[i]
|
266
|
+
state.tShift[i + startLine] = oldTShift[i]
|
267
|
+
state.sCount[i + startLine] = oldSCount[i]
|
268
|
+
state.bsCount[i + startLine] = oldBSCount[i]
|
133
269
|
end
|
134
270
|
state.blkIndent = oldIndent
|
135
271
|
return true
|