motion-markdown-it-plugins 8.4.1 → 8.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,161 @@
1
+ module MotionMarkdownItPlugins
2
+ module EmojiPlugin
3
+ module Data
4
+ module Light
5
+ EMOJIES_DEF_LIGHT = {
6
+ "grinning" => "😀",
7
+ "smiley" => "😃",
8
+ "smile" => "😄",
9
+ "grin" => "😁",
10
+ "laughing" => "😆",
11
+ "satisfied" => "😆",
12
+ "sweat_smile" => "😅",
13
+ "joy" => "😂",
14
+ "blush" => "😊",
15
+ "innocent" => "😇",
16
+ "wink" => "😉",
17
+ "relieved" => "😌",
18
+ "heart_eyes" => "😍",
19
+ "kissing_heart" => "😘",
20
+ "kissing" => "😗",
21
+ "kissing_smiling_eyes" => "😙",
22
+ "kissing_closed_eyes" => "😚",
23
+ "yum" => "😋",
24
+ "stuck_out_tongue_winking_eye" => "😜",
25
+ "stuck_out_tongue_closed_eyes" => "😝",
26
+ "stuck_out_tongue" => "😛",
27
+ "sunglasses" => "😎",
28
+ "smirk" => "😏",
29
+ "unamused" => "😒",
30
+ "disappointed" => "😞",
31
+ "pensive" => "😔",
32
+ "worried" => "😟",
33
+ "confused" => "😕",
34
+ "persevere" => "😣",
35
+ "confounded" => "😖",
36
+ "tired_face" => "😫",
37
+ "weary" => "😩",
38
+ "angry" => "😠",
39
+ "rage" => "😡",
40
+ "pout" => "😡",
41
+ "no_mouth" => "😶",
42
+ "neutral_face" => "😐",
43
+ "expressionless" => "😑",
44
+ "hushed" => "😯",
45
+ "frowning" => "😦",
46
+ "anguished" => "😧",
47
+ "open_mouth" => "😮",
48
+ "astonished" => "😲",
49
+ "dizzy_face" => "😵",
50
+ "flushed" => "😳",
51
+ "scream" => "😱",
52
+ "fearful" => "😨",
53
+ "cold_sweat" => "😰",
54
+ "cry" => "😢",
55
+ "disappointed_relieved" => "😥",
56
+ "sob" => "😭",
57
+ "sweat" => "😓",
58
+ "sleepy" => "😪",
59
+ "sleeping" => "😴",
60
+ "mask" => "😷",
61
+ "smiling_imp" => "😈",
62
+ "smiley_cat" => "😺",
63
+ "smile_cat" => "😸",
64
+ "joy_cat" => "😹",
65
+ "heart_eyes_cat" => "😻",
66
+ "smirk_cat" => "😼",
67
+ "kissing_cat" => "😽",
68
+ "scream_cat" => "🙀",
69
+ "crying_cat_face" => "😿",
70
+ "pouting_cat" => "😾",
71
+ "fist_raised" => "✊",
72
+ "fist" => "✊",
73
+ "v" => "✌️",
74
+ "point_up" => "☝️",
75
+ "hand" => "✋",
76
+ "raised_hand" => "✋",
77
+ "cat" => "🐱",
78
+ "mouse" => "🐭",
79
+ "cow" => "🐮",
80
+ "monkey_face" => "🐵",
81
+ "star" => "⭐️",
82
+ "sparkles" => "✨",
83
+ "zap" => "⚡️",
84
+ "sunny" => "☀️",
85
+ "cloud" => "☁️",
86
+ "snowflake" => "❄️",
87
+ "umbrella" => "☔️",
88
+ "coffee" => "☕️",
89
+ "airplane" => "✈️",
90
+ "anchor" => "⚓️",
91
+ "watch" => "⌚️",
92
+ "phone" => "☎️",
93
+ "telephone" => "☎️",
94
+ "hourglass" => "⌛️",
95
+ "email" => "✉️",
96
+ "envelope" => "✉️",
97
+ "scissors" => "✂️",
98
+ "black_nib" => "✒️",
99
+ "pencil2" => "✏️",
100
+ "heart" => "❤️",
101
+ "aries" => "♈️",
102
+ "taurus" => "♉️",
103
+ "gemini" => "♊️",
104
+ "cancer" => "♋️",
105
+ "leo" => "♌️",
106
+ "virgo" => "♍️",
107
+ "libra" => "♎️",
108
+ "scorpius" => "♏️",
109
+ "sagittarius" => "♐️",
110
+ "capricorn" => "♑️",
111
+ "aquarius" => "♒️",
112
+ "pisces" => "♓️",
113
+ "eight_pointed_black_star" => "✴️",
114
+ "x" => "❌",
115
+ "hotsprings" => "♨️",
116
+ "exclamation" => "❗️",
117
+ "heavy_exclamation_mark" => "❗️",
118
+ "grey_exclamation" => "❕",
119
+ "question" => "❓",
120
+ "grey_question" => "❔",
121
+ "bangbang" => "‼️",
122
+ "interrobang" => "⁉️",
123
+ "part_alternation_mark" => "〽️",
124
+ "warning" => "⚠️",
125
+ "recycle" => "♻️",
126
+ "white_check_mark" => "✅",
127
+ "sparkle" => "❇️",
128
+ "eight_spoked_asterisk" => "✳️",
129
+ "negative_squared_cross_mark" => "❎",
130
+ "m" => "Ⓜ️",
131
+ "wheelchair" => "♿️",
132
+ "information_source" => "ℹ️",
133
+ "heavy_plus_sign" => "➕",
134
+ "heavy_minus_sign" => "➖",
135
+ "heavy_division_sign" => "➗",
136
+ "heavy_multiplication_x" => "✖️",
137
+ "tm" => "™️",
138
+ "copyright" => "©️",
139
+ "registered" => "®️",
140
+ "wavy_dash" => "〰️",
141
+ "curly_loop" => "➰",
142
+ "loop" => "➿",
143
+ "heavy_check_mark" => "✔️",
144
+ "ballot_box_with_check" => "☑️",
145
+ "white_circle" => "⚪️",
146
+ "black_circle" => "⚫️",
147
+ "black_small_square" => "▪️",
148
+ "white_small_square" => "▫️",
149
+ "black_medium_small_square" => "◾️",
150
+ "white_medium_small_square" => "◽️",
151
+ "black_medium_square" => "◼️",
152
+ "white_medium_square" => "◻️",
153
+ "black_large_square" => "⬛️",
154
+ "white_large_square" => "⬜️",
155
+ "black_joker" => "🃏",
156
+ "mahjong" => "🀄️"
157
+ }.freeze
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,36 @@
1
+ module MotionMarkdownItPlugins
2
+ module EmojiPlugin
3
+ module Data
4
+ module Shortcuts
5
+ EMOJIES_DEF_SHORTCUTS = {
6
+ 'angry' => [ '>:(', '>:-(' ],
7
+ 'blush' => [ ':")', ':-")' ],
8
+ 'broken_heart' => [ '</3', '<\\3' ],
9
+ # :\ and :-\ not used because of conflict with markdown escaping
10
+ 'confused' => [ ':/', ':-/' ], # twemoji shows question
11
+ 'cry' => [ ":'(", ":'-(", ':,(', ':,-(' ],
12
+ 'frowning' => [ ':(', ':-(' ],
13
+ 'heart' => [ '<3' ],
14
+ 'imp' => [ ']:(', ']:-(' ],
15
+ 'innocent' => [ 'o:)', 'O:)', 'o:-)', 'O:-)', '0:)', '0:-)' ],
16
+ 'joy' => [ ":')", ":'-)", ':,)', ':,-)', ":'D", ":'-D", ':,D', ':,-D' ],
17
+ 'kissing' => [ ':*', ':-*' ],
18
+ 'laughing' => [ 'x-)', 'X-)' ],
19
+ 'neutral_face' => [ ':|', ':-|' ],
20
+ 'open_mouth' => [ ':o', ':-o', ':O', ':-O' ],
21
+ 'rage' => [ ':@', ':-@' ],
22
+ 'smile' => [ ':D', ':-D' ],
23
+ 'smiley' => [ ':)', ':-)' ],
24
+ 'smiling_imp' => [ ']:)', ']:-)' ],
25
+ 'sob' => [ ":,'(", ":,'-(", ';(', ';-(' ],
26
+ 'stuck_out_tongue' => [ ':P', ':-P' ],
27
+ 'sunglasses' => [ '8-)', 'B-)' ],
28
+ 'sweat' => [ ',:(', ',:-(' ],
29
+ 'sweat_smile' => [ ',:)', ',:-)' ],
30
+ 'unamused' => [ ':s', ':-S', ':z', ':-Z', ':$', ':-$' ],
31
+ 'wink' => [ ';)', ';-)' ]
32
+ }.freeze
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,72 @@
1
+ module MotionMarkdownItPlugins
2
+ module EmojiPlugin
3
+ module NormalizeOpts
4
+ #------------------------------------------------------------------------------
5
+ def quoteRE(str)
6
+ str.gsub(/([.?*+^$\[\]\\(){}|-])/, '\\\\\1')
7
+ end
8
+
9
+ #------------------------------------------------------------------------------
10
+ def normalize_opts(options)
11
+ emojies = stringify_keys(options[:defs])
12
+ shortcuts = stringify_keys(options[:shortcuts])
13
+
14
+ # Filter emojies by whitelist, if needed
15
+ if options[:enabled].length > 0
16
+ emojies = emojies.keys.reduce({}) do |acc, key|
17
+ if options[:enabled].include?(key)
18
+ acc[key] = emojies[key]
19
+ end
20
+
21
+ acc
22
+ end
23
+ end
24
+
25
+ # Flatten shortcuts to simple object: { alias: emoji_name }
26
+ shortcuts = shortcuts.keys.reduce({}) do |acc, key|
27
+ # Skip aliases for filtered emojies, to reduce regexp
28
+ next acc if !emojies[key]
29
+
30
+ if shortcuts[key].is_a?(Array)
31
+ shortcuts[key].each do |alias_value|
32
+ acc[alias_value] = key
33
+ end
34
+ next acc
35
+ end
36
+
37
+ acc[shortcuts[key]] = key
38
+ acc
39
+ end
40
+
41
+ # Compile regexp
42
+ names = emojies.keys
43
+ .map { |name| ':' + name + ':' }
44
+ .concat(shortcuts.keys)
45
+ .sort
46
+ .reverse
47
+ .map { |name| quoteRE(name) }
48
+ .join('|')
49
+
50
+ scanRE = Regexp.new(names)
51
+ replaceRE = Regexp.new(names)
52
+
53
+ return {
54
+ defs: emojies,
55
+ shortcuts: shortcuts,
56
+ scanRE: scanRE,
57
+ replaceRE: replaceRE
58
+ }
59
+ end
60
+
61
+ private
62
+
63
+ # convert keys from symbols into strings
64
+ #------------------------------------------------------------------------------
65
+ def stringify_keys(symbol_hash)
66
+ result = {}
67
+ symbol_hash.each_key { |key| result[key.to_s] = symbol_hash[key] }
68
+ result
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,9 @@
1
+ module MotionMarkdownItPlugins
2
+ module EmojiPlugin
3
+ module Render
4
+ def emoji_html(tokens, idx) #, options, env
5
+ return tokens[idx].content
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,101 @@
1
+ # Emojies & shortcuts replacement logic.
2
+ #
3
+ # Note: In theory, it could be faster to parse :smile: in inline chain and
4
+ # leave only shortcuts here. But, who care...
5
+ #------------------------------------------------------------------------------
6
+ module MotionMarkdownItPlugins
7
+ module EmojiPlugin
8
+ module Replace
9
+ Z_RE_SRC = UCMicro::Categories::Z::REGEX.source
10
+ P_RE_SRC = UCMicro::Categories::P::REGEX.source
11
+ CC_RE_SRC = UCMicro::Categories::Cc::REGEX.source
12
+ ZPCC_RE = Regexp.new([Z_RE_SRC, P_RE_SRC, CC_RE_SRC].join('|'))
13
+
14
+ #------------------------------------------------------------------------------
15
+ def create_rule(md, emojies, shortcuts, scanRE, replaceRE)
16
+ @emojies = emojies
17
+ @shortcuts = shortcuts
18
+ @scanRE = scanRE
19
+ @replaceRE = replaceRE
20
+
21
+ return lambda { |state| emoji_replace(state) }
22
+ end
23
+
24
+ #------------------------------------------------------------------------------
25
+ def splitTextToken(text, level, token_class)
26
+ last_pos = 0
27
+ nodes = []
28
+
29
+ text.gsub(@replaceRE) do |match|
30
+ match_data = Regexp.last_match
31
+ offset = match_data.offset(0)[0]
32
+ src = match_data.string
33
+
34
+ # Validate emoji name
35
+ if @shortcuts.include?(match)
36
+ # replace shortcut with full name
37
+ emoji_name = @shortcuts[match]
38
+
39
+ # Don't allow letters before any shortcut (as in no ":/" in http://)
40
+ next if offset > 0 && !(ZPCC_RE =~ src[offset - 1])
41
+
42
+ # Don't allow letters after any shortcut
43
+ next if (offset + match.length < src.length) && !(ZPCC_RE =~ src[offset + match.length])
44
+ else
45
+ emoji_name = match.slice(1...-1)
46
+ end
47
+
48
+ # Add new tokens to pending list
49
+ if offset > last_pos
50
+ token = token_class.new('text', '', 0)
51
+ token.content = text.slice(last_pos...offset)
52
+ nodes.push(token)
53
+ end
54
+
55
+ token = token_class.new('emoji', '', 0)
56
+ token.markup = emoji_name
57
+ token.content = @emojies[emoji_name]
58
+ nodes.push(token)
59
+
60
+ last_pos = offset + match.length
61
+ end
62
+
63
+ if last_pos < text.length
64
+ token = token_class.new('text', '', 0)
65
+ token.content = text.slice(last_pos..-1)
66
+ nodes.push(token)
67
+ end
68
+
69
+ return nodes
70
+ end
71
+
72
+ #------------------------------------------------------------------------------
73
+ def emoji_replace(state)
74
+ blockTokens = state.tokens
75
+ autolinkLevel = 0
76
+
77
+ 0.upto(blockTokens.length - 1) do |j|
78
+ next if blockTokens[j].type != 'inline'
79
+ tokens = blockTokens[j].children
80
+
81
+ # We scan from the end, to keep position when new tags added.
82
+ # Use reversed logic in links start/end match
83
+ (tokens.length - 1).downto(0) do |i|
84
+ token = tokens[i]
85
+
86
+ if token.type == 'link_open' || token.type == 'link_close'
87
+ autolinkLevel -= token.nesting if token.info == 'auto'
88
+ end
89
+
90
+ if token.type == 'text' && autolinkLevel == 0 && @scanRE =~ token.content
91
+ # replace current node
92
+ blockTokens[j].children = tokens = arrayReplaceAt(
93
+ tokens, i, splitTextToken(token.content, token.level, MarkdownIt::Token)
94
+ )
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,5 +1,5 @@
1
1
  module MotionMarkdownItPlugins
2
2
 
3
- VERSION = '8.4.1'
3
+ VERSION = '8.4.2'
4
4
 
5
5
  end
@@ -0,0 +1,115 @@
1
+ fixture_dir = fixture_path('emoji/fixtures')
2
+
3
+ #------------------------------------------------------------------------------
4
+ describe 'markdown-it-emoji' do
5
+ describe 'default' do
6
+ md = MarkdownIt::Parser.new
7
+ md.use(MotionMarkdownItPlugins::Emoji)
8
+
9
+ generate(File.join(fixture_dir, 'default/emoji.txt'), md)
10
+ generate(File.join(fixture_dir, 'default/aliases.txt'), md)
11
+ generate(File.join(fixture_dir, 'full.txt'), md)
12
+ end
13
+
14
+ describe 'options' do
15
+ md = MarkdownIt::Parser.new
16
+ md.use(MotionMarkdownItPlugins::Emoji, {
17
+ defs: {
18
+ one: '!!!one!!!',
19
+ fifty: '!!50!!'
20
+ },
21
+ shortcuts: {
22
+ fifty: [ ':50', '|50' ],
23
+ one: ':uno'
24
+ }
25
+ })
26
+
27
+ generate(File.join(fixture_dir, 'options.txt'), md)
28
+ end
29
+
30
+ describe 'whitelist' do
31
+ md = MarkdownIt::Parser.new
32
+ md.use(MotionMarkdownItPlugins::Emoji, { enabled: [ 'smile', 'grin' ] })
33
+
34
+ generate(File.join(fixture_dir, 'whitelist.txt'), md)
35
+ end
36
+
37
+ describe 'autolinks' do
38
+ md = MarkdownIt::Parser.new({ linkify: true })
39
+ md.use(MotionMarkdownItPlugins::Emoji)
40
+
41
+ generate(File.join(fixture_dir, 'autolinks.txt'), md)
42
+ end
43
+ end
44
+
45
+ describe 'markdown-it-emoji-light' do
46
+ describe 'default' do
47
+ md = MarkdownIt::Parser.new
48
+ md.use(MotionMarkdownItPlugins::EmojiLight)
49
+
50
+ generate(File.join(fixture_dir, 'default/emoji.txt'), md)
51
+ generate(File.join(fixture_dir, 'default/aliases.txt'), md)
52
+ generate(File.join(fixture_dir, 'light.txt'), md)
53
+ end
54
+
55
+ describe 'options' do
56
+ md = MarkdownIt::Parser.new
57
+ md.use(MotionMarkdownItPlugins::EmojiLight, {
58
+ defs: {
59
+ one: '!!!one!!!',
60
+ fifty: '!!50!!'
61
+ },
62
+ shortcuts: {
63
+ fifty: [ ':50', '|50' ],
64
+ one: ':uno'
65
+ }
66
+ })
67
+
68
+ generate(File.join(fixture_dir, 'options.txt'), md)
69
+ end
70
+
71
+ describe 'whitelist' do
72
+ md = MarkdownIt::Parser.new
73
+ md.use(MotionMarkdownItPlugins::EmojiLight, { enabled: [ 'smile', 'grin' ] })
74
+
75
+ generate(File.join(fixture_dir, 'whitelist.txt'), md)
76
+ end
77
+
78
+ describe 'autolinks' do
79
+ md = MarkdownIt::Parser.new({ linkify: true })
80
+ md.use(MotionMarkdownItPlugins::EmojiLight)
81
+
82
+ generate(File.join(fixture_dir, 'autolinks.txt'), md)
83
+ end
84
+ end
85
+
86
+ describe 'integrity' do
87
+ it 'all shortcuts should exist' do
88
+ MotionMarkdownItPlugins::EmojiPlugin::Data::Shortcuts::EMOJIES_DEF_SHORTCUTS.keys.each do |name|
89
+ expect(MotionMarkdownItPlugins::EmojiPlugin::Data::Full::EMOJIES_DEF_FULL[name]).not_to eq nil
90
+ end
91
+ end
92
+
93
+ it 'no chars with "uXXXX" names allowed' do
94
+ MotionMarkdownItPlugins::EmojiPlugin::Data::Full::EMOJIES_DEF_FULL.keys.each do |name|
95
+ expect(/^u[0-9a-b]{4,}$/ =~ name).to eq nil
96
+ end
97
+ end
98
+
99
+ it 'all light chars should exist' do
100
+
101
+ visible = File.read(File.join(fixture_dir, 'visible.txt'))
102
+
103
+ available = MotionMarkdownItPlugins::EmojiPlugin::Data::Light::EMOJIES_DEF_LIGHT.keys.map do |k|
104
+ MotionMarkdownItPlugins::EmojiPlugin::Data::Light::EMOJIES_DEF_LIGHT[k].gsub(/\uFE0F/, '')
105
+ end
106
+
107
+ missed = ''
108
+
109
+ visible.each_char do |ch|
110
+ missed << ch unless available.include?(ch)
111
+ end
112
+
113
+ expect(missed.length).to eq 0
114
+ end
115
+ end