motion-markdown-it-plugins 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|