qiita-markdown 0.43.0 → 0.44.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +14 -200
  3. data/CHANGELOG.md +10 -0
  4. data/README.md +2 -2
  5. data/benchmark/heading_anchor_rendering.rb +1 -1
  6. data/lib/qiita/markdown/embed/figma.rb +11 -0
  7. data/lib/qiita/markdown/filters/checkbox.rb +1 -1
  8. data/lib/qiita/markdown/filters/code_block.rb +13 -13
  9. data/lib/qiita/markdown/filters/custom_block.rb +1 -0
  10. data/lib/qiita/markdown/filters/external_link.rb +2 -0
  11. data/lib/qiita/markdown/filters/final_sanitizer.rb +118 -117
  12. data/lib/qiita/markdown/filters/footnote.rb +2 -0
  13. data/lib/qiita/markdown/filters/group_mention.rb +2 -2
  14. data/lib/qiita/markdown/filters/image_link.rb +6 -6
  15. data/lib/qiita/markdown/filters/inline_code_color.rb +8 -8
  16. data/lib/qiita/markdown/filters/mention.rb +11 -9
  17. data/lib/qiita/markdown/filters/simplify.rb +1 -0
  18. data/lib/qiita/markdown/filters/syntax_highlight.rb +4 -4
  19. data/lib/qiita/markdown/filters/truncate.rb +1 -3
  20. data/lib/qiita/markdown/filters/user_input_sanitizer.rb +26 -23
  21. data/lib/qiita/markdown/greenmat/heading_rendering.rb +2 -2
  22. data/lib/qiita/markdown/greenmat/html_toc_renderer.rb +1 -1
  23. data/lib/qiita/markdown/transformers/filter_attributes.rb +1 -0
  24. data/lib/qiita/markdown/transformers/filter_iframe.rb +2 -2
  25. data/lib/qiita/markdown/transformers/filter_script.rb +1 -1
  26. data/lib/qiita/markdown/transformers/strip_invalid_node.rb +1 -3
  27. data/lib/qiita/markdown/version.rb +1 -1
  28. data/lib/qiita/markdown.rb +1 -0
  29. data/qiita-markdown.gemspec +6 -6
  30. data/spec/qiita/markdown/filters/checkbox_spec.rb +42 -0
  31. data/spec/qiita/markdown/processor_spec.rb +54 -16
  32. data/spec/qiita/markdown/summary_processor_spec.rb +2 -2
  33. metadata +96 -99
@@ -12,41 +12,41 @@ module Qiita
12
12
  class FinalSanitizer < HTML::Pipeline::Filter
13
13
  RULE = {
14
14
  attributes: {
15
- "a" => [
16
- "data-hovercard-target-name",
17
- "data-hovercard-target-type",
18
- "href",
19
- "rel",
15
+ "a" => %w[
16
+ data-hovercard-target-name
17
+ data-hovercard-target-type
18
+ href
19
+ rel
20
20
  ],
21
21
  "blockquote" => Embed::Tweet::ATTRIBUTES,
22
- "iframe" => [
23
- "allowfullscreen",
24
- "frameborder",
25
- "height",
26
- "marginheight",
27
- "marginwidth",
28
- "scrolling",
29
- "src",
30
- "style",
31
- "width",
22
+ "iframe" => %w[
23
+ allowfullscreen
24
+ frameborder
25
+ height
26
+ marginheight
27
+ marginwidth
28
+ scrolling
29
+ src
30
+ style
31
+ width
32
32
  ],
33
33
  "img" => [
34
34
  "src",
35
35
  ],
36
- "input" => [
37
- "checked",
38
- "disabled",
39
- "type",
36
+ "input" => %w[
37
+ checked
38
+ disabled
39
+ type
40
40
  ],
41
- "div" => [
42
- "itemscope",
43
- "itemtype",
41
+ "div" => %w[
42
+ itemscope
43
+ itemtype
44
44
  ],
45
45
  "p" => Embed::CodePen::ATTRIBUTES,
46
- "script" => [
47
- "async",
48
- "src",
49
- "type",
46
+ "script" => %w[
47
+ async
48
+ src
49
+ type
50
50
  ].concat(
51
51
  Embed::SpeekerDeck::ATTRIBUTES,
52
52
  Embed::Docswell::ATTRIBUTES,
@@ -60,103 +60,104 @@ module Qiita
60
60
  "th" => [
61
61
  "style",
62
62
  ],
63
- "video" => [
64
- "src",
65
- "autoplay",
66
- "controls",
67
- "loop",
68
- "muted",
69
- "poster",
63
+ "video" => %w[
64
+ src
65
+ autoplay
66
+ controls
67
+ loop
68
+ muted
69
+ poster
70
70
  ],
71
- all: [
72
- "abbr",
73
- "align",
74
- "alt",
75
- "border",
76
- "cellpadding",
77
- "cellspacing",
78
- "cite",
79
- "class",
80
- "color",
81
- "cols",
82
- "colspan",
83
- "data-lang",
84
- "datetime",
85
- "height",
86
- "hreflang",
87
- "id",
88
- "itemprop",
89
- "lang",
90
- "name",
91
- "rowspan",
92
- "tabindex",
93
- "target",
94
- "title",
95
- "width",
71
+ all: %w[
72
+ abbr
73
+ align
74
+ alt
75
+ border
76
+ cellpadding
77
+ cellspacing
78
+ cite
79
+ class
80
+ color
81
+ cols
82
+ colspan
83
+ data-lang
84
+ datetime
85
+ height
86
+ hreflang
87
+ id
88
+ itemprop
89
+ lang
90
+ name
91
+ rowspan
92
+ tabindex
93
+ target
94
+ title
95
+ width
96
96
  ],
97
97
  },
98
98
  css: {
99
- properties: [
100
- "text-align",
101
- "background-color",
99
+ properties: %w[
100
+ background-color
101
+ border
102
+ text-align
102
103
  ],
103
104
  },
104
- elements: [
105
- "a",
106
- "b",
107
- "blockquote",
108
- "br",
109
- "code",
110
- "dd",
111
- "del",
112
- "details",
113
- "div",
114
- "dl",
115
- "dt",
116
- "em",
117
- "font",
118
- "h1",
119
- "h2",
120
- "h3",
121
- "h4",
122
- "h5",
123
- "h6",
124
- "h7",
125
- "h8",
126
- "hr",
127
- "i",
128
- "img",
129
- "input",
130
- "ins",
131
- "kbd",
132
- "li",
133
- "ol",
134
- "p",
135
- "pre",
136
- "q",
137
- "rp",
138
- "rt",
139
- "ruby",
140
- "s",
141
- "samp",
142
- "script",
143
- "iframe",
144
- "span",
145
- "strike",
146
- "strong",
147
- "sub",
148
- "summary",
149
- "sup",
150
- "table",
151
- "tbody",
152
- "td",
153
- "tfoot",
154
- "th",
155
- "thead",
156
- "tr",
157
- "tt",
158
- "ul",
159
- "var",
105
+ elements: %w[
106
+ a
107
+ b
108
+ blockquote
109
+ br
110
+ code
111
+ dd
112
+ del
113
+ details
114
+ div
115
+ dl
116
+ dt
117
+ em
118
+ font
119
+ h1
120
+ h2
121
+ h3
122
+ h4
123
+ h5
124
+ h6
125
+ h7
126
+ h8
127
+ hr
128
+ i
129
+ img
130
+ input
131
+ ins
132
+ kbd
133
+ li
134
+ ol
135
+ p
136
+ pre
137
+ q
138
+ rp
139
+ rt
140
+ ruby
141
+ s
142
+ samp
143
+ script
144
+ iframe
145
+ span
146
+ strike
147
+ strong
148
+ sub
149
+ summary
150
+ sup
151
+ table
152
+ tbody
153
+ td
154
+ tfoot
155
+ th
156
+ thead
157
+ tr
158
+ tt
159
+ ul
160
+ var
160
161
  ],
161
162
  protocols: {
162
163
  "a" => {
@@ -6,6 +6,7 @@ module Qiita
6
6
  doc.search("sup > a").each do |a|
7
7
  footnote = find_footnote(a)
8
8
  next unless footnote
9
+
9
10
  a[:title] = footnote.text.gsub(/\A\n/, "").gsub(/ ↩\n\z/, "")
10
11
  end
11
12
  doc
@@ -16,6 +17,7 @@ module Qiita
16
17
  def find_footnote(a)
17
18
  href = a["href"]
18
19
  return nil if !href || href.match(/\A#fn\d+\z/).nil?
20
+
19
21
  doc.search(href).first
20
22
  end
21
23
  end
@@ -21,9 +21,9 @@ module Qiita
21
21
  GROUP_IDENTIFIER_PATTERN = %r{
22
22
  (?:^|\W)
23
23
  @((?>[a-z\d][a-z\d-]{2,31}))
24
- \/
24
+ /
25
25
  ([A-Za-z\d][A-Za-z\d-]{0,62}[A-Za-z\d])
26
- (?!\/)
26
+ (?!/)
27
27
  (?=
28
28
  \.+[ \t\W]|
29
29
  \.+$|
@@ -4,12 +4,12 @@ module Qiita
4
4
  class ImageLink < HTML::Pipeline::Filter
5
5
  def call
6
6
  doc.search("img").each do |img|
7
- unless img.ancestors.any? { |ancestor| ancestor.name == "a" }
8
- outer = Nokogiri::HTML.fragment(%(<a href="#{img['src']}" target="_blank"></a>))
9
- inner = img.clone
10
- outer.at("a").add_child(inner)
11
- img.replace(outer)
12
- end
7
+ next if img.ancestors.any? { |ancestor| ancestor.name == "a" }
8
+
9
+ outer = Nokogiri::HTML.fragment(%(<a href="#{img['src']}" target="_blank"></a>))
10
+ inner = img.clone
11
+ outer.at("a").add_child(inner)
12
+ img.replace(outer)
13
13
  end
14
14
  doc
15
15
  end
@@ -6,15 +6,15 @@ module Qiita
6
6
 
7
7
  REGEXPS = Regexp.union(
8
8
  /\#(?:\h{3}|\h{6})/,
9
- /rgba?\(\s*(?:\d+(?:\,|\s)\s*){2}\d+\s*\)/,
10
- /rgba?\(\s*(?:\d+%(?:\,|\s)\s*){2}\d+%\s*\)/,
11
- /rgba?\(\s*(?:\d+\,\s*){3}\d*\.?\d+%?\s*\)/,
12
- /rgba?\(\s*(?:\d+\s*){2}\d+\s*\/\s*\d?\.?\d+%?\s*\)/,
13
- /rgba?\(\s*(?:\d+%\s*){2}\d+%\s*\/\s*\d?\.?\d+%?\s*\)/,
14
- /hsla?\(\s*\d+(?:deg|rad|grad|turn)?\,\s*\d+%\,\s*\d+%\s*\)/,
9
+ /rgba?\(\s*(?:\d+(?:,|\s)\s*){2}\d+\s*\)/,
10
+ /rgba?\(\s*(?:\d+%(?:,|\s)\s*){2}\d+%\s*\)/,
11
+ /rgba?\(\s*(?:\d+,\s*){3}\d*\.?\d+%?\s*\)/,
12
+ %r{rgba?\(\s*(?:\d+\s*){2}\d+\s*/\s*\d?\.?\d+%?\s*\)},
13
+ %r{rgba?\(\s*(?:\d+%\s*){2}\d+%\s*/\s*\d?\.?\d+%?\s*\)},
14
+ /hsla?\(\s*\d+(?:deg|rad|grad|turn)?,\s*\d+%,\s*\d+%\s*\)/,
15
15
  /hsla?\(\s*\d+(?:deg|rad|grad|turn)?\s+\d+%\s+\d+%\s*\)/,
16
- /hsla?\(\s*\d+(?:deg|rad|grad|turn)?\,\s*(?:\d+%\,\s*){2}\d?\.?\d+%?\s*\)/,
17
- /hsla?\(\s*\d+(?:deg|rad|grad|turn)?\s+\d+%\s+\d+%\s*\/\s*\d?\.?\d+%?\s*\)/,
16
+ /hsla?\(\s*\d+(?:deg|rad|grad|turn)?,\s*(?:\d+%,\s*){2}\d?\.?\d+%?\s*\)/,
17
+ %r{hsla?\(\s*\d+(?:deg|rad|grad|turn)?\s+\d+%\s+\d+%\s*/\s*\d?\.?\d+%?\s*\)},
18
18
  )
19
19
 
20
20
  COLOR_CODE_PATTERN = /\A\s*(#{REGEXPS})\s*\z/
@@ -8,17 +8,17 @@ module Qiita
8
8
  class Mention < HTML::Pipeline::MentionFilter
9
9
  IGNORE_PARENTS = ::HTML::Pipeline::MentionFilter::IGNORE_PARENTS + Set["blockquote"]
10
10
 
11
- MentionPattern = /
11
+ MentionPattern = %r{
12
12
  (?:^|\W)
13
- @((?>[\w][\w-]{0,30}\w(?:@github)?))
14
- (?!\/)
13
+ @((?>\w[\w-]{0,30}\w(?:@github)?))
14
+ (?!/)
15
15
  (?=
16
16
  \.+[ \t\W]|
17
17
  \.+$|
18
18
  [^0-9a-zA-Z_.]|
19
19
  $
20
20
  )
21
- /ix
21
+ }ix
22
22
 
23
23
  # @note Override to use another IGNORE_PARENTS
24
24
  def call
@@ -28,8 +28,10 @@ module Qiita
28
28
  content = node.to_html
29
29
  next unless content.include?("@")
30
30
  next if has_ancestor?(node, IGNORE_PARENTS)
31
+
31
32
  html = mention_link_filter(content, base_url, info_url, username_pattern)
32
33
  next if html == content
34
+
33
35
  node.replace(html)
34
36
  end
35
37
  doc
@@ -38,22 +40,22 @@ module Qiita
38
40
  # @note Override to use customized MentionPattern and allowed_usernames logic.
39
41
  def mention_link_filter(text, _, _, _)
40
42
  text.gsub(MentionPattern) do |match|
41
- name = $1
43
+ name = ::Regexp.last_match(1)
42
44
  case
43
45
  when allowed_usernames && name == "all"
44
46
  result[:mentioned_usernames] |= allowed_usernames
45
47
  match.sub(
46
48
  "@#{name}",
47
- %[<a href="/" class="user-mention" title="#{name}">@#{name}</a>]
49
+ %(<a href="/" class="user-mention" title="#{name}">@#{name}</a>),
48
50
  )
49
- when allowed_usernames && !allowed_usernames.include?(name) || name == "all"
51
+ when (allowed_usernames && !allowed_usernames.include?(name)) || name == "all"
50
52
  match
51
53
  else
52
54
  result[:mentioned_usernames] |= [name]
53
55
  url = File.join(base_url, name)
54
56
  match.sub(
55
57
  "@#{name}",
56
- %[<a href="#{url}" class="user-mention js-hovercard" title="#{name}" data-hovercard-target-type="user" data-hovercard-target-name="#{name}">@#{name}</a>]
58
+ %(<a href="#{url}" class="user-mention js-hovercard" title="#{name}" data-hovercard-target-type="user" data-hovercard-target-name="#{name}">@#{name}</a>),
57
59
  )
58
60
  end
59
61
  end
@@ -66,7 +68,7 @@ module Qiita
66
68
  end
67
69
 
68
70
  def has_ancestor?(node, tags)
69
- super || node.parent.parent && node.parent.parent["class"] == "code-lang"
71
+ super || (node.parent.parent && node.parent.parent["class"] == "code-lang")
70
72
  end
71
73
  end
72
74
  end
@@ -38,6 +38,7 @@ module Qiita
38
38
  doc.traverse do |node|
39
39
  next unless node.element?
40
40
  next if SIMPLE_ELEMENTS.include?(node.name)
41
+
41
42
  node.replace(node.children)
42
43
  end
43
44
  end
@@ -54,7 +54,7 @@ module Qiita
54
54
  end
55
55
 
56
56
  def call
57
- outer = Nokogiri::HTML.fragment(%Q[<div class="code-frame" data-lang="#{language}">])
57
+ outer = Nokogiri::HTML.fragment(%(<div class="code-frame" data-lang="#{language}">))
58
58
  frame = outer.at("div")
59
59
  frame.add_child(filename_node) if filename
60
60
  frame.add_child(highlighted_node)
@@ -72,7 +72,7 @@ module Qiita
72
72
  end
73
73
 
74
74
  def filename_node
75
- %Q[<div class="code-lang"><span class="bold">#{filename}</span></div>]
75
+ %(<div class="code-lang"><span class="bold">#{filename}</span></div>)
76
76
  end
77
77
 
78
78
  def has_inline_php?
@@ -87,7 +87,7 @@ module Qiita
87
87
  if specific_language && Rouge::Lexer.find(specific_language)
88
88
  begin
89
89
  highlight(specific_language).presence or raise
90
- rescue
90
+ rescue StandardError
91
91
  highlight(@default_language)
92
92
  end
93
93
  else
@@ -100,7 +100,7 @@ module Qiita
100
100
  end
101
101
 
102
102
  def language_node
103
- Nokogiri::HTML.fragment(%Q[<div class="code-frame" data-lang="#{language}"></div>])
103
+ Nokogiri::HTML.fragment(%(<div class="code-frame" data-lang="#{language}"></div>))
104
104
  end
105
105
 
106
106
  def specific_language
@@ -51,9 +51,7 @@ module Qiita
51
51
  node.content.each_char.with_index do |char, index|
52
52
  current_char_is_blank = char.strip.empty?
53
53
 
54
- if !@previous_char_was_blank || !current_char_is_blank
55
- @current_length += 1
56
- end
54
+ @current_length += 1 if !@previous_char_was_blank || !current_char_is_blank
57
55
 
58
56
  @previous_char_was_blank = current_char_is_blank
59
57
 
@@ -10,24 +10,24 @@ module Qiita
10
10
  summary sup table tbody td tfoot th thead tr ul var
11
11
  ],
12
12
  attributes: {
13
- "a" => %w[class href rel title],
13
+ "a" => %w[class href rel title],
14
14
  "blockquote" => %w[cite] + Embed::Tweet::ATTRIBUTES,
15
- "code" => %w[data-metadata],
16
- "div" => %w[class data-type data-metadata],
17
- "font" => %w[color],
18
- "h1" => %w[id],
19
- "h2" => %w[id],
20
- "h3" => %w[id],
21
- "h4" => %w[id],
22
- "h5" => %w[id],
23
- "h6" => %w[id],
24
- "img" => %w[alt height src title width],
25
- "ins" => %w[cite datetime],
26
- "li" => %w[id],
27
- "p" => Embed::CodePen::ATTRIBUTES,
28
- "q" => %w[cite],
29
- "script" => %w[async src id].concat(Embed::SpeekerDeck::ATTRIBUTES, Embed::Docswell::ATTRIBUTES),
30
- "iframe" => %w[
15
+ "code" => %w[data-metadata],
16
+ "div" => %w[class data-type data-metadata],
17
+ "font" => %w[color],
18
+ "h1" => %w[id],
19
+ "h2" => %w[id],
20
+ "h3" => %w[id],
21
+ "h4" => %w[id],
22
+ "h5" => %w[id],
23
+ "h6" => %w[id],
24
+ "img" => %w[alt height src title width],
25
+ "ins" => %w[cite datetime],
26
+ "li" => %w[id],
27
+ "p" => Embed::CodePen::ATTRIBUTES,
28
+ "q" => %w[cite],
29
+ "script" => %w[async src id].concat(Embed::SpeekerDeck::ATTRIBUTES, Embed::Docswell::ATTRIBUTES),
30
+ "iframe" => %w[
31
31
  allowfullscreen
32
32
  frameborder
33
33
  height
@@ -38,17 +38,20 @@ module Qiita
38
38
  style
39
39
  width
40
40
  ],
41
- "sup" => %w[id],
42
- "td" => %w[colspan rowspan style],
43
- "th" => %w[colspan rowspan style],
41
+ "sup" => %w[id],
42
+ "td" => %w[colspan rowspan style],
43
+ "th" => %w[colspan rowspan style],
44
44
  },
45
45
  protocols: {
46
- "a" => { "href" => ["http", "https", "mailto", :relative] },
46
+ "a" => { "href" => ["http", "https", "mailto", :relative] },
47
47
  "blockquote" => { "cite" => ["http", "https", :relative] },
48
- "q" => { "cite" => ["http", "https", :relative] },
48
+ "q" => { "cite" => ["http", "https", :relative] },
49
49
  },
50
50
  css: {
51
- properties: %w[text-align],
51
+ properties: %w[
52
+ text-align
53
+ border
54
+ ],
52
55
  },
53
56
  transformers: [
54
57
  Transformers::FilterAttributes,
@@ -18,11 +18,11 @@ module Qiita
18
18
  end
19
19
 
20
20
  def to_s
21
- fail NotImplementedError
21
+ raise NotImplementedError
22
22
  end
23
23
 
24
24
  def increment
25
- fail NotImplementedError
25
+ raise NotImplementedError
26
26
  end
27
27
 
28
28
  private
@@ -12,7 +12,7 @@ module Qiita
12
12
 
13
13
  # https://github.com/vmg/redcarpet/blob/v3.2.3/ext/redcarpet/html.c#L609-L642
14
14
  def header(text, level)
15
- @level_offset = level - 1 unless @level_offset
15
+ @level_offset ||= level - 1
16
16
 
17
17
  level -= @level_offset
18
18
  level = 1 if level < 1
@@ -37,6 +37,7 @@ module Qiita
37
37
 
38
38
  def transform
39
39
  return unless FILTERS.key?(name)
40
+
40
41
  FILTERS[name].each_pair do |attr, pattern|
41
42
  filter_attribute(attr, pattern) if node.attributes.key?(attr)
42
43
  end
@@ -2,14 +2,14 @@ module Qiita
2
2
  module Markdown
3
3
  module Transformers
4
4
  class FilterIframe
5
- URL_WHITE_LIST = [
6
- ].flatten.freeze
5
+ URL_WHITE_LIST = [].flatten.freeze
7
6
 
8
7
  HOST_WHITE_LIST = [
9
8
  Embed::Youtube::SCRIPT_HOSTS,
10
9
  Embed::SlideShare::SCRIPT_HOST,
11
10
  Embed::GoogleSlide::SCRIPT_HOST,
12
11
  Embed::Docswell::SCRIPT_HOSTS,
12
+ Embed::Figma::SCRIPT_HOST,
13
13
  ].flatten.freeze
14
14
 
15
15
  def self.call(**args)
@@ -45,7 +45,7 @@ module Qiita
45
45
  def host_of(url)
46
46
  if url
47
47
  scheme = URI.parse(url).scheme
48
- Addressable::URI.parse(url).host if ["http", "https"].include? scheme
48
+ Addressable::URI.parse(url).host if %w[http https].include? scheme
49
49
  end
50
50
  rescue Addressable::URI::InvalidURIError, URI::InvalidURIError
51
51
  nil
@@ -12,9 +12,7 @@ module Qiita
12
12
  end
13
13
 
14
14
  def transform
15
- if has_invalid_list_node? || has_invalid_table_node?
16
- node.replace(node.children)
17
- end
15
+ node.replace(node.children) if has_invalid_list_node? || has_invalid_table_node?
18
16
  end
19
17
 
20
18
  private
@@ -1,5 +1,5 @@
1
1
  module Qiita
2
2
  module Markdown
3
- VERSION = "0.43.0"
3
+ VERSION = "0.44.1"
4
4
  end
5
5
  end
@@ -15,6 +15,7 @@ require "qiita/markdown/embed/slide_share"
15
15
  require "qiita/markdown/embed/google_slide"
16
16
  require "qiita/markdown/embed/speeker_deck"
17
17
  require "qiita/markdown/embed/docswell"
18
+ require "qiita/markdown/embed/figma"
18
19
  require "qiita/markdown/transformers/filter_attributes"
19
20
  require "qiita/markdown/transformers/filter_script"
20
21
  require "qiita/markdown/transformers/filter_iframe"