motion-markdown-it-plugins 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +18 -0
- data/lib/motion-markdown-it-plugins.rb +24 -0
- data/lib/motion-markdown-it-plugins/abbr/abbr.rb +130 -0
- data/lib/motion-markdown-it-plugins/checkbox_replace/checkbox_replace.rb +103 -0
- data/lib/motion-markdown-it-plugins/container/container.rb +154 -0
- data/lib/motion-markdown-it-plugins/deflist/deflist.rb +185 -0
- data/lib/motion-markdown-it-plugins/ins/ins.rb +136 -0
- data/lib/motion-markdown-it-plugins/mark/mark.rb +136 -0
- data/lib/motion-markdown-it-plugins/sub/sub.rb +70 -0
- data/lib/motion-markdown-it-plugins/sup/sup.rb +71 -0
- data/lib/motion-markdown-it-plugins/version.rb +5 -0
- data/spec/abbr/abbr_spec.rb +8 -0
- data/spec/checkbox_replace/checkbox_replace_spec.rb +64 -0
- data/spec/container/api_spec.rb +76 -0
- data/spec/container/default_spec.rb +8 -0
- data/spec/container/misc_spec.rb +17 -0
- data/spec/deflist/deflist_spec.rb +9 -0
- data/spec/ins/ins_spec.rb +9 -0
- data/spec/mark/mark_spec.rb +9 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/sub/sub_spec.rb +9 -0
- data/spec/sup/sup_spec.rb +9 -0
- data/spec/testgen_helper.rb +80 -0
- metadata +92 -0
@@ -0,0 +1,136 @@
|
|
1
|
+
# Based on Javascript version: https://github.com/markdown-it/markdown-it-ins
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
|
4
|
+
module MotionMarkdownItPlugins
|
5
|
+
class Ins
|
6
|
+
extend MarkdownIt::Common::Utils
|
7
|
+
|
8
|
+
#------------------------------------------------------------------------------
|
9
|
+
def self.init_plugin(md)
|
10
|
+
md.inline.ruler.before('emphasis', 'ins', lambda { |state, silent| Ins.insert(state, silent) })
|
11
|
+
end
|
12
|
+
|
13
|
+
# parse sequence of markers,
|
14
|
+
# "start" should point at a valid marker
|
15
|
+
#------------------------------------------------------------------------------
|
16
|
+
def self.scanDelims(state, start)
|
17
|
+
pos = start
|
18
|
+
can_open = true
|
19
|
+
can_close = true
|
20
|
+
max = state.posMax
|
21
|
+
marker = state.src.charCodeAt(start)
|
22
|
+
|
23
|
+
# treat beginning of the line as a whitespace
|
24
|
+
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : 0x20
|
25
|
+
|
26
|
+
while (pos < max && state.src.charCodeAt(pos) == marker)
|
27
|
+
pos += 1
|
28
|
+
end
|
29
|
+
|
30
|
+
if (pos >= max)
|
31
|
+
can_open = false
|
32
|
+
end
|
33
|
+
|
34
|
+
count = pos - start
|
35
|
+
|
36
|
+
# treat end of the line as a whitespace
|
37
|
+
nextChar = pos < max ? state.src.charCodeAt(pos) : 0x20
|
38
|
+
|
39
|
+
isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(lastChar.chr(Encoding::UTF_8))
|
40
|
+
isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(nextChar.chr(Encoding::UTF_8))
|
41
|
+
|
42
|
+
isLastWhiteSpace = isWhiteSpace(lastChar)
|
43
|
+
isNextWhiteSpace = isWhiteSpace(nextChar)
|
44
|
+
|
45
|
+
if (isNextWhiteSpace)
|
46
|
+
can_open = false
|
47
|
+
elsif (isNextPunctChar)
|
48
|
+
if (!(isLastWhiteSpace || isLastPunctChar))
|
49
|
+
can_open = false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if (isLastWhiteSpace)
|
54
|
+
can_close = false
|
55
|
+
elsif (isLastPunctChar)
|
56
|
+
if (!(isNextWhiteSpace || isNextPunctChar))
|
57
|
+
can_close = false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
return {can_open: can_open, can_close: can_close, delims: count}
|
62
|
+
end
|
63
|
+
|
64
|
+
#------------------------------------------------------------------------------
|
65
|
+
def self.insert(state, silent)
|
66
|
+
max = state.posMax
|
67
|
+
start = state.pos
|
68
|
+
marker = state.src.charCodeAt(start)
|
69
|
+
|
70
|
+
return false if (marker != 0x2B) # '+'
|
71
|
+
return false if (silent) # don't run any pairs in validation mode
|
72
|
+
|
73
|
+
res = scanDelims(state, start)
|
74
|
+
startCount = res[:delims]
|
75
|
+
|
76
|
+
if (!res[:can_open])
|
77
|
+
state.pos += startCount
|
78
|
+
# Earlier we checked !silent, but this implementation does not need it
|
79
|
+
state.pending += state.src.slice(start...state.pos)
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
|
83
|
+
stack = (startCount / 2).floor
|
84
|
+
return false if (stack <= 0)
|
85
|
+
state.pos = start + startCount
|
86
|
+
|
87
|
+
while (state.pos < max)
|
88
|
+
if (state.src.charCodeAt(state.pos) == marker)
|
89
|
+
res = scanDelims(state, state.pos)
|
90
|
+
count = res[:delims]
|
91
|
+
tagCount = (count / 2).floor
|
92
|
+
if (res[:can_close])
|
93
|
+
if (tagCount >= stack)
|
94
|
+
state.pos += count - 2
|
95
|
+
found = true
|
96
|
+
break
|
97
|
+
end
|
98
|
+
stack -= tagCount
|
99
|
+
state.pos += count
|
100
|
+
next
|
101
|
+
end
|
102
|
+
|
103
|
+
stack += tagCount if (res[:can_open])
|
104
|
+
state.pos += count
|
105
|
+
next
|
106
|
+
end
|
107
|
+
|
108
|
+
state.md.inline.skipToken(state)
|
109
|
+
end
|
110
|
+
|
111
|
+
if (!found)
|
112
|
+
# parser failed to find ending tag, so it's not valid emphasis
|
113
|
+
state.pos = start
|
114
|
+
return false
|
115
|
+
end
|
116
|
+
|
117
|
+
# found!
|
118
|
+
state.posMax = state.pos
|
119
|
+
state.pos = start + 2
|
120
|
+
|
121
|
+
# Earlier we checked !silent, but this implementation does not need it
|
122
|
+
token = state.push('ins_open', 'ins', 1)
|
123
|
+
token.markup = marker.chr * 2
|
124
|
+
|
125
|
+
state.md.inline.tokenize(state);
|
126
|
+
|
127
|
+
token = state.push('ins_close', 'ins', -1);
|
128
|
+
token.markup = marker.chr * 2
|
129
|
+
|
130
|
+
state.pos = state.posMax + 2
|
131
|
+
state.posMax = max
|
132
|
+
return true
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# Based on Javascript version: https://github.com/markdown-it/markdown-it-mark
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
|
4
|
+
module MotionMarkdownItPlugins
|
5
|
+
class Mark
|
6
|
+
extend MarkdownIt::Common::Utils
|
7
|
+
|
8
|
+
#------------------------------------------------------------------------------
|
9
|
+
def self.init_plugin(md)
|
10
|
+
md.inline.ruler.before('emphasis', 'mark', lambda { |state, silent| Mark.mark(state, silent) })
|
11
|
+
end
|
12
|
+
|
13
|
+
# parse sequence of markers,
|
14
|
+
# "start" should point at a valid marker
|
15
|
+
#------------------------------------------------------------------------------
|
16
|
+
def self.scanDelims(state, start)
|
17
|
+
pos = start
|
18
|
+
can_open = true
|
19
|
+
can_close = true
|
20
|
+
max = state.posMax
|
21
|
+
marker = state.src.charCodeAt(start)
|
22
|
+
|
23
|
+
# treat beginning of the line as a whitespace
|
24
|
+
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : 0x20
|
25
|
+
|
26
|
+
while (pos < max && state.src.charCodeAt(pos) == marker)
|
27
|
+
pos += 1
|
28
|
+
end
|
29
|
+
|
30
|
+
if (pos >= max)
|
31
|
+
can_open = false
|
32
|
+
end
|
33
|
+
|
34
|
+
count = pos - start
|
35
|
+
|
36
|
+
# treat end of the line as a whitespace
|
37
|
+
nextChar = pos < max ? state.src.charCodeAt(pos) : 0x20
|
38
|
+
|
39
|
+
isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(lastChar.chr(Encoding::UTF_8))
|
40
|
+
isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(nextChar.chr(Encoding::UTF_8))
|
41
|
+
|
42
|
+
isLastWhiteSpace = isWhiteSpace(lastChar)
|
43
|
+
isNextWhiteSpace = isWhiteSpace(nextChar)
|
44
|
+
|
45
|
+
if (isNextWhiteSpace)
|
46
|
+
can_open = false
|
47
|
+
elsif (isNextPunctChar)
|
48
|
+
if (!(isLastWhiteSpace || isLastPunctChar))
|
49
|
+
can_open = false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if (isLastWhiteSpace)
|
54
|
+
can_close = false
|
55
|
+
elsif (isLastPunctChar)
|
56
|
+
if (!(isNextWhiteSpace || isNextPunctChar))
|
57
|
+
can_close = false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
return {can_open: can_open, can_close: can_close, delims: count}
|
62
|
+
end
|
63
|
+
|
64
|
+
#------------------------------------------------------------------------------
|
65
|
+
def self.mark(state, silent)
|
66
|
+
max = state.posMax
|
67
|
+
start = state.pos
|
68
|
+
marker = state.src.charCodeAt(start)
|
69
|
+
|
70
|
+
return false if (marker != 0x3D) # '='
|
71
|
+
return false if (silent) # don't run any pairs in validation mode
|
72
|
+
|
73
|
+
res = scanDelims(state, start)
|
74
|
+
startCount = res[:delims]
|
75
|
+
|
76
|
+
if (!res[:can_open])
|
77
|
+
state.pos += startCount
|
78
|
+
# Earlier we checked !silent, but this implementation does not need it
|
79
|
+
state.pending += state.src.slice(start...state.pos)
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
|
83
|
+
stack = (startCount / 2).floor
|
84
|
+
return false if (stack <= 0)
|
85
|
+
state.pos = start + startCount
|
86
|
+
|
87
|
+
while (state.pos < max)
|
88
|
+
if (state.src.charCodeAt(state.pos) == marker)
|
89
|
+
res = scanDelims(state, state.pos)
|
90
|
+
count = res[:delims]
|
91
|
+
tagCount = (count / 2).floor
|
92
|
+
if (res[:can_close])
|
93
|
+
if (tagCount >= stack)
|
94
|
+
state.pos += count - 2
|
95
|
+
found = true
|
96
|
+
break
|
97
|
+
end
|
98
|
+
stack -= tagCount
|
99
|
+
state.pos += count
|
100
|
+
next
|
101
|
+
end
|
102
|
+
|
103
|
+
stack += tagCount if (res[:can_open])
|
104
|
+
state.pos += count
|
105
|
+
next
|
106
|
+
end
|
107
|
+
|
108
|
+
state.md.inline.skipToken(state)
|
109
|
+
end
|
110
|
+
|
111
|
+
if (!found)
|
112
|
+
# parser failed to find ending tag, so it's not valid emphasis
|
113
|
+
state.pos = start
|
114
|
+
return false
|
115
|
+
end
|
116
|
+
|
117
|
+
# found!
|
118
|
+
state.posMax = state.pos
|
119
|
+
state.pos = start + 2
|
120
|
+
|
121
|
+
# Earlier we checked !silent, but this implementation does not need it
|
122
|
+
token = state.push('mark_open', 'mark', 1)
|
123
|
+
token.markup = marker.chr * 2
|
124
|
+
|
125
|
+
state.md.inline.tokenize(state)
|
126
|
+
|
127
|
+
token = state.push('mark_close', 'mark', -1)
|
128
|
+
token.markup = marker.chr * 2
|
129
|
+
|
130
|
+
state.pos = state.posMax + 2
|
131
|
+
state.posMax = max
|
132
|
+
return true
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Process ~subscript~
|
2
|
+
#
|
3
|
+
# Based on Javascript version: https://github.com/markdown-it/markdown-it-sub
|
4
|
+
#------------------------------------------------------------------------------
|
5
|
+
|
6
|
+
module MotionMarkdownItPlugins
|
7
|
+
class Sub
|
8
|
+
extend MarkdownIt::Common::Utils
|
9
|
+
|
10
|
+
# same as UNESCAPE_MD_RE plus a space
|
11
|
+
UNESCAPE_RE = /\\([ \!\"\#\$\%\&\'\(\)\*\+\,\-.\/:;<=>?@\[\\\]^_`{|}~])/
|
12
|
+
|
13
|
+
#------------------------------------------------------------------------------
|
14
|
+
def self.init_plugin(md)
|
15
|
+
md.inline.ruler.after('emphasis', 'sub', lambda { |state, silent| Sub.subscript(state, silent) })
|
16
|
+
end
|
17
|
+
|
18
|
+
#------------------------------------------------------------------------------
|
19
|
+
def self.subscript(state, silent)
|
20
|
+
max = state.posMax
|
21
|
+
start = state.pos
|
22
|
+
|
23
|
+
return false if (state.src.charCodeAt(start) != 0x7E) # '~'
|
24
|
+
return false if (silent) # don't run any pairs in validation mode
|
25
|
+
return false if (start + 2 >= max)
|
26
|
+
|
27
|
+
state.pos = start + 1
|
28
|
+
|
29
|
+
while (state.pos < max)
|
30
|
+
if (state.src.charCodeAt(state.pos) == 0x7E) # '~'
|
31
|
+
found = true
|
32
|
+
break
|
33
|
+
end
|
34
|
+
|
35
|
+
state.md.inline.skipToken(state)
|
36
|
+
end
|
37
|
+
|
38
|
+
if (!found || start + 1 == state.pos)
|
39
|
+
state.pos = start
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
content = state.src.slice((start + 1)...state.pos)
|
44
|
+
|
45
|
+
# don't allow unescaped spaces/newlines inside
|
46
|
+
if (content.match(/(^|[^\\])(\\\\)*\s/))
|
47
|
+
state.pos = start
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
|
51
|
+
# found!
|
52
|
+
state.posMax = state.pos
|
53
|
+
state.pos = start + 1
|
54
|
+
|
55
|
+
# Earlier we checked !silent, but this implementation does not need it
|
56
|
+
token = state.push('sub_open', 'sub', 1)
|
57
|
+
token.markup = '~'
|
58
|
+
|
59
|
+
token = state.push('text', '', 0)
|
60
|
+
token.content = content.gsub(UNESCAPE_RE, '\1')
|
61
|
+
|
62
|
+
token = state.push('sub_close', 'sub', -1)
|
63
|
+
token.markup = '~'
|
64
|
+
|
65
|
+
state.pos = state.posMax + 1
|
66
|
+
state.posMax = max
|
67
|
+
return true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Process ^superscript^
|
2
|
+
#
|
3
|
+
# Based on Javascript version: https://github.com/markdown-it/markdown-it-sup
|
4
|
+
#------------------------------------------------------------------------------
|
5
|
+
|
6
|
+
module MotionMarkdownItPlugins
|
7
|
+
class Sup
|
8
|
+
extend MarkdownIt::Common::Utils
|
9
|
+
|
10
|
+
# same as UNESCAPE_MD_RE plus a space
|
11
|
+
UNESCAPE_RE = /\\([ \!\"\#\$\%\&\'\(\)\*\+\,\-.\/:;<=>?@\[\\\]^_`{|}~])/
|
12
|
+
|
13
|
+
#------------------------------------------------------------------------------
|
14
|
+
def self.init_plugin(md)
|
15
|
+
md.inline.ruler.after('emphasis', 'sup', lambda { |state, silent| Sup.superscript(state, silent) })
|
16
|
+
end
|
17
|
+
|
18
|
+
#------------------------------------------------------------------------------
|
19
|
+
def self.superscript(state, silent)
|
20
|
+
max = state.posMax
|
21
|
+
start = state.pos
|
22
|
+
|
23
|
+
return false if (state.src.charCodeAt(start) != 0x5E) # '^'
|
24
|
+
return false if (silent) # don't run any pairs in validation mode
|
25
|
+
return false if (start + 2 >= max)
|
26
|
+
|
27
|
+
state.pos = start + 1
|
28
|
+
|
29
|
+
while (state.pos < max)
|
30
|
+
if (state.src.charCodeAt(state.pos) == 0x5E) # '^'
|
31
|
+
found = true
|
32
|
+
break
|
33
|
+
end
|
34
|
+
|
35
|
+
state.md.inline.skipToken(state)
|
36
|
+
end
|
37
|
+
|
38
|
+
if (!found || start + 1 == state.pos)
|
39
|
+
state.pos = start
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
content = state.src.slice((start + 1)...state.pos)
|
44
|
+
|
45
|
+
# don't allow unescaped spaces/newlines inside
|
46
|
+
if (content.match(/(^|[^\\])(\\\\)*\s/))
|
47
|
+
state.pos = start
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
|
51
|
+
# found!
|
52
|
+
state.posMax = state.pos
|
53
|
+
state.pos = start + 1
|
54
|
+
|
55
|
+
# Earlier we checked !silent, but this implementation does not need it
|
56
|
+
token = state.push('sup_open', 'sup', 1)
|
57
|
+
token.markup = '^'
|
58
|
+
|
59
|
+
token = state.push('text', '', 0)
|
60
|
+
token.content = content.gsub(UNESCAPE_RE, '\1')
|
61
|
+
|
62
|
+
token = state.push('sup_close', 'sup', -1)
|
63
|
+
token.markup = '^'
|
64
|
+
|
65
|
+
state.pos = state.posMax + 1
|
66
|
+
state.posMax = max
|
67
|
+
return true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
fixture_dir = fixture_path('abbr/fixtures')
|
2
|
+
|
3
|
+
#------------------------------------------------------------------------------
|
4
|
+
describe 'markdown-it-abbr' do
|
5
|
+
md = MarkdownIt::Parser.new
|
6
|
+
md.use(MotionMarkdownItPlugins::Abbr)
|
7
|
+
generate(File.join(fixture_dir, 'abbr.txt'), md)
|
8
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
fixture_dir = fixture_path('checkbox_replace/fixtures')
|
2
|
+
|
3
|
+
describe 'markdown-it-checkbox' do
|
4
|
+
|
5
|
+
#------------------------------------------------------------------------------
|
6
|
+
describe 'markdown-it-checkbox()' do
|
7
|
+
md = MarkdownIt::Parser.new
|
8
|
+
md.use(MotionMarkdownItPlugins::CheckboxReplace, {divWrap: false})
|
9
|
+
specfile = File.join(fixture_dir, 'checkbox.txt')
|
10
|
+
tests = get_tests(specfile)
|
11
|
+
tests.each do |t|
|
12
|
+
define_test(t, md)
|
13
|
+
end
|
14
|
+
|
15
|
+
#------------------------------------------------------------------------------
|
16
|
+
it 'should pass irrelevant markdown' do
|
17
|
+
md = MarkdownIt::Parser.new
|
18
|
+
res = md.render('# test')
|
19
|
+
expect(res).to eq "<h1>test</h1>\n"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#------------------------------------------------------------------------------
|
24
|
+
describe 'markdown-it-checkbox(options)' do
|
25
|
+
|
26
|
+
#------------------------------------------------------------------------------
|
27
|
+
it 'should should optionally wrap arround a div layer' do
|
28
|
+
md = MarkdownIt::Parser.new
|
29
|
+
md.use(MotionMarkdownItPlugins::CheckboxReplace, {divWrap: true})
|
30
|
+
res = md.render('[X] test written')
|
31
|
+
expect(res).to eq '<p>' +
|
32
|
+
'<div class="checkbox">' +
|
33
|
+
'<input type="checkbox" id="checkbox0" checked="true">' +
|
34
|
+
'<label for="checkbox0">test written</label>' +
|
35
|
+
'</div>' +
|
36
|
+
"</p>\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
#------------------------------------------------------------------------------
|
40
|
+
it 'should should optionally change class of div layer' do
|
41
|
+
md = MarkdownIt::Parser.new
|
42
|
+
md.use(MotionMarkdownItPlugins::CheckboxReplace, {divWrap: true, divClass: 'cb'})
|
43
|
+
res = md.render('[X] test written')
|
44
|
+
expect(res).to eq '<p>' +
|
45
|
+
'<div class="cb">' +
|
46
|
+
'<input type="checkbox" id="checkbox0" checked="true">' +
|
47
|
+
'<label for="checkbox0">test written</label>' +
|
48
|
+
'</div>' +
|
49
|
+
"</p>\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
#------------------------------------------------------------------------------
|
53
|
+
it 'should should optionally change the id' do
|
54
|
+
md = MarkdownIt::Parser.new
|
55
|
+
md.use(MotionMarkdownItPlugins::CheckboxReplace, {idPrefix: 'cb'})
|
56
|
+
res = md.render('[X] test written')
|
57
|
+
expect(res).to eq '<p>' +
|
58
|
+
'<input type="checkbox" id="cb0" checked="true">' +
|
59
|
+
'<label for="cb0">test written</label>' +
|
60
|
+
"</p>\n"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|