qiita-markdown 0.43.0 → 0.44.1

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.
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"