qiita-markdown 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of qiita-markdown might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 562609fe58337615b673c876377987e51854fcd4
4
- data.tar.gz: cf66ad7199327b467430b2f967f0ebe23aac7be1
3
+ metadata.gz: d82079377c4ce72ef4a32e6a1c30d7b63f6e8f87
4
+ data.tar.gz: 8b080251af685ea2b9547266edfb4d6a554ca5be
5
5
  SHA512:
6
- metadata.gz: 253c48a2cef9c1421796f8461e128f89c523cb908b47b7086d81d771bf79ad2647546b885ea9732aadda53147001db3a03fd1885a4eead4f641e7e2dd753b114
7
- data.tar.gz: f844f7a22030b9142b4aff9292a81bd7f8ca36387a6ade044baabe930535eeb7a1dfc770d72b0d3ba50867eec7f12adfeb0d07c4739eb2d134fb9343bbc78d8d
6
+ metadata.gz: c544a2bd69d30ca9386f03b24e688a939576179e604ca0c666b122220083943c884bf8610e15e4ca904d060e75c79860b3753f832d4cc7da9f8e4e3c6b62e1ef
7
+ data.tar.gz: 415900299a6b8277d64c0b60143b76277f2f26fedaf6e8698f5032843e88569b93c2ebae3d5ec2aad291cbc15cd40b0496598edeb45c02c87efd8e4cf24e0cf4
data/.travis.yml CHANGED
@@ -1,9 +1,11 @@
1
1
  before_install:
2
- - sudo apt-get update -qq
3
- - sudo apt-get install libicu-dev
2
+ - sudo apt-get update -qq
3
+ - sudo apt-get install libicu-dev
4
4
  language: ruby
5
5
  rvm:
6
- - 2.0.0
6
+ - 2.0.0
7
+ - 2.1
8
+ - 2.2
7
9
  env:
8
10
  global:
9
11
  secure: n8eyxYYfxLApgR4YGKqbrOgGlraIyLyoql4K4DvLZV4kqfGf9LLsPdP7Shudqrv5k2h8xIwnJVnwcPZx9YCu5WWYrJd7vmivpU2j52LwFPYRM+GFNcu7TXmzcNSPG8agnc5We9amF5zJY6XSTpzWpxyqfIwEZM75iR6XXuHuLFk=
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 0.5.0
2
+ - Add renderers Qiita::Markdown::Greenmat::HTMLRenderer and Qiita::Markdown::Greenmat::HTMLToCRenderer which can be passed to `Redcarpet::Markdown.new` and generate consistent heading fragment identifiers.
3
+
1
4
  ## 0.4.2
2
5
  - Fix bug on SummaryProcessor with mention
3
6
 
data/README.md CHANGED
@@ -9,7 +9,6 @@ Qiita-specified markdown processor.
9
9
  * Emoji
10
10
  * Syntax highlighting
11
11
  * Mention
12
- * Task list
13
12
  * Footnotes
14
13
 
15
14
  ## Basic Usage
@@ -0,0 +1,4 @@
1
+ inherit_from: ../.rubocop.yml
2
+
3
+ Style/AsciiComments:
4
+ Enabled: false
@@ -0,0 +1,245 @@
1
+ require "benchmark/ips"
2
+ require "qiita/markdown"
3
+
4
+ # The old implementation
5
+ module PostProcess
6
+ class Processor < Qiita::Markdown::Processor
7
+ def filters
8
+ @filters ||= [
9
+ Filters::Greenmat,
10
+ Filters::Toc,
11
+ ]
12
+ end
13
+ end
14
+
15
+ module Filters
16
+ class Greenmat < HTML::Pipeline::TextFilter
17
+ class << self
18
+ # Memoize.
19
+ # @return [Greenmat::Markdown]
20
+ def renderer
21
+ @renderer ||= ::Greenmat::Markdown.new(
22
+ ::Greenmat::Render::HTML.new(
23
+ hard_wrap: true,
24
+ ),
25
+ autolink: true,
26
+ fenced_code_blocks: true,
27
+ footnotes: true,
28
+ no_intra_emphasis: true,
29
+ no_mention_emphasis: true,
30
+ strikethrough: true,
31
+ tables: true,
32
+ )
33
+ end
34
+ end
35
+
36
+ # @return [Nokogiri::HTML::DocumentFragment]
37
+ def call
38
+ Nokogiri::HTML.fragment(self.class.renderer.render(@text))
39
+ end
40
+ end
41
+
42
+ class Toc < HTML::Pipeline::Filter
43
+ def call
44
+ counter = Hash.new(0)
45
+ doc.css("h1, h2, h3, h4, h5, h6").each do |node|
46
+ heading = Heading.new(node, counter)
47
+ heading.add_anchor_element if heading.has_first_child?
48
+ heading.increment
49
+ end
50
+ doc
51
+ end
52
+
53
+ class Heading
54
+ def initialize(node, counter)
55
+ @node = node
56
+ @counter = counter
57
+ end
58
+
59
+ def add_anchor_element
60
+ first_child.add_previous_sibling(anchor_element)
61
+ end
62
+
63
+ def anchor_element
64
+ %[<span id="#{suffixed_id}" class="fragment"></span><a href="##{suffixed_id}"><i class="fa fa-link"></i></a>]
65
+ end
66
+
67
+ def content
68
+ @content ||= node.children.first
69
+ end
70
+
71
+ def count
72
+ @counter[id]
73
+ end
74
+
75
+ def first_child
76
+ @first_child ||= @node.children.first
77
+ end
78
+
79
+ def has_count?
80
+ count > 0
81
+ end
82
+
83
+ def has_first_child?
84
+ !!first_child
85
+ end
86
+
87
+ def id
88
+ @node.text.downcase.gsub(/[^\p{Word}\- ]/u, "").gsub(" ", "-")
89
+ end
90
+
91
+ def increment
92
+ @counter[id] += 1
93
+ end
94
+
95
+ def suffix
96
+ has_count? ? "-#{count}" : ""
97
+ end
98
+
99
+ def suffixed_id
100
+ "#{id}#{suffix}"
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # The new implementation
108
+ module Rendering
109
+ class Processor < Qiita::Markdown::Processor
110
+ def filters
111
+ @filters ||= [
112
+ Filters::Greenmat,
113
+ ]
114
+ end
115
+ end
116
+
117
+ module Filters
118
+ class Greenmat < HTML::Pipeline::TextFilter
119
+ def call
120
+ Nokogiri::HTML.fragment(greenmat.render(@text))
121
+ end
122
+
123
+ private
124
+
125
+ # Memoize.
126
+ # @return [Greenmat::Markdown]
127
+ def greenmat
128
+ @renderer ||= ::Greenmat::Markdown.new(
129
+ HTMLRenderer.new(hard_wrap: true, with_toc_data: true),
130
+ autolink: true,
131
+ fenced_code_blocks: true,
132
+ footnotes: true,
133
+ no_intra_emphasis: true,
134
+ no_mention_emphasis: true,
135
+ strikethrough: true,
136
+ tables: true,
137
+ )
138
+ end
139
+
140
+ class HTMLRenderer < ::Greenmat::Render::HTML
141
+ def initialize(extensions = {})
142
+ super
143
+ @with_toc_data = extensions[:with_toc_data]
144
+ end
145
+
146
+ def header(text, level)
147
+ heading = heading_class.new(text, level, counter)
148
+ heading.to_s.tap do
149
+ heading.increment
150
+ end
151
+ end
152
+
153
+ private
154
+
155
+ def heading_class
156
+ @heading_class ||= (@with_toc_data ? HeadingWithAnchor : Heading)
157
+ end
158
+
159
+ def counter
160
+ @counter ||= Hash.new(0)
161
+ end
162
+
163
+ Heading = Struct.new(:text, :level, :counter) do
164
+ # For reference, C implementation of Redcarpet::Render::HTML#header is the following:
165
+ # https://github.com/vmg/redcarpet/blob/v3.2.3/ext/redcarpet/html.c#L281-L296
166
+ def to_s
167
+ "\n<h#{level}>#{content}</h#{level}>\n"
168
+ end
169
+
170
+ def increment
171
+ # no-op
172
+ end
173
+
174
+ private
175
+
176
+ def content
177
+ text
178
+ end
179
+ end
180
+
181
+ class HeadingWithAnchor < Heading
182
+ def increment
183
+ counter[id] += 1
184
+ end
185
+
186
+ private
187
+
188
+ def content
189
+ anchor_element + text
190
+ end
191
+
192
+ def anchor_element
193
+ %(<span id="#{suffixed_id}" class="fragment"></span><a href="##{suffixed_id}"><i class="fa fa-link"></i></a>)
194
+ end
195
+
196
+ def count
197
+ counter[id]
198
+ end
199
+
200
+ def has_count?
201
+ count > 0
202
+ end
203
+
204
+ def id
205
+ @id ||= text.downcase.gsub(/[^\p{Word}\- ]/u, "").gsub(" ", "-")
206
+ end
207
+
208
+ def suffix
209
+ has_count? ? "-#{count}" : ""
210
+ end
211
+
212
+ def suffixed_id
213
+ @suffixed_id ||= "#{id}#{suffix}"
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ markdown = File.read(File.join(File.dirname(__FILE__), "sample.md"))
222
+
223
+ Benchmark.ips do |benchmark|
224
+ benchmark.report("post process") do
225
+ PostProcess::Processor.new.call(markdown)
226
+ end
227
+
228
+ benchmark.report("rendering") do
229
+ Rendering::Processor.new.call(markdown)
230
+ end
231
+
232
+ benchmark.compare!
233
+ end
234
+
235
+ # Calculating -------------------------------------
236
+ # post process 4 i/100ms
237
+ # rendering 22 i/100ms
238
+ # -------------------------------------------------
239
+ # post process 48.3 (±16.6%) i/s - 236 in 5.027265s
240
+ # rendering 227.3 (±15.8%) i/s - 1122 in 5.084983s
241
+ #
242
+ # Comparison:
243
+ # rendering: 227.3 i/s
244
+ # post process: 48.3 i/s - 4.70x slower
245
+ #
@@ -0,0 +1,317 @@
1
+ Markdown記法 チートシート
2
+ Markdown記法のチートシートです。
3
+ 本ページではQiitaで使用可能なMarkdownのみ掲載しているため、一部原文と異なります。
4
+ Markdownの原文については、[Daring Fireball: Markdown Syntax Documentation]
5
+ (http://daringfireball.net/projects/markdown/syntax.php)をご覧下さい。
6
+ また、コードに関する記法は[GitHub Flavored Markdown](http://github.github.com/github-flavored-markdown/)に準拠しています。
7
+ Qiitaでシンタックスハイライト可能な言語一覧については、 [シンタックスハイライト可能な言語](http://qiita.com/Qiita/items/e84f5aad7757afce82ba) をご覧下さい。
8
+
9
+ ## Code - コードの挿入
10
+
11
+ たとえば、Rubyで記述したコードをファイル名「qiita.rb」として投稿したいときは、 **バッククオート** を使用して以下のように投稿するとシンタックスハイライトが適用されます。
12
+ **コードブロック上下に空行を挿入しないと正しく表示されないことがあります。**
13
+
14
+ > (空行)
15
+ > \`\`\`ruby:qiita.rb
16
+ > puts 'The best way to log and share programmers knowledge.'
17
+ > \`\`\`
18
+ > (空行)
19
+
20
+ **結果**
21
+
22
+ ```ruby:qiita.rb
23
+ puts 'The best way to log and share programmers knowledge.'
24
+ ```
25
+
26
+ また、コードをインライン表示することも可能です。
27
+
28
+ > \` puts 'Qiita'` はプログラマのための技術情報共有サービスです。
29
+
30
+ **結果**
31
+
32
+ ` puts 'Qiita'` はプログラマのための技術情報共有サービスです。
33
+
34
+ インラインコードがn個連続するバッククオートを含む場合、n+1連続のバッククオートで囲みます。
35
+
36
+ > \`\` \`バッククオート\` \`\` や \`\`\` \`\`2連続バッククオート\`\` \`\`\` も記述できます。
37
+
38
+ **結果**
39
+
40
+ `` `バッククオート` `` や ``` ``2連続バッククオート`` ``` も記述できます。
41
+
42
+ ### Gist連携について
43
+
44
+ ##### GitHubアカウントでQiitaにログインされている場合
45
+
46
+ 投稿時、Octocatアイコンにチェックを入れていただくと連携を行います。
47
+ コードを含むアイテムを投稿するとコード部分を抽出し、同じ内容がGistにも投稿される仕組みになっています。
48
+
49
+ Gistとの連携は、
50
+
51
+ * コードの投稿
52
+ * Qiita側でのコードの編集
53
+
54
+ の2点について連携しています。
55
+ Gist側でコードを編集されても、 **Qiitaには反映されません** のでご注意下さい。
56
+
57
+ ## Format Text - テキストの装飾
58
+
59
+ ### Headers - 見出し
60
+
61
+ * \# これはH1タグです
62
+ * \## これはH2タグです
63
+ * \###### これはH6タグです
64
+
65
+ ### Emphasis - 強調
66
+
67
+ ```markdown
68
+ _イタリック体_を使うには _ か * で囲みます。
69
+ **太字**を使うには __ か ** で囲みます。
70
+ ```
71
+
72
+ _イタリック体_を使うには _ か * で囲みます。
73
+ **太字**を使うには __ か ** で囲みます。
74
+
75
+ ### Strikethrough - 打ち消し線
76
+
77
+ ```markdown
78
+ 打ち消し線を使うには ~~ で囲みます。 ~~打ち消し~~
79
+ ```
80
+
81
+ 打ち消し線を使うには ~~ で囲みます。 ~~打ち消し~~
82
+
83
+ イタリックや太文字と同様に前後に **半角スペース** か **改行文字** が必要です。
84
+
85
+ ## Lists - リスト
86
+
87
+ ### Disc型
88
+
89
+ * 文頭に「*」「+」「-」のいずれかを入れるとDisc型リストになります
90
+ * 要点をまとめる際に便利です
91
+ * リストを挿入する際は、 **リストの上下に空行がないと正しく表示されません。また「*」「+」「-」の後にはスペースが必要です**
92
+
93
+ ### Decimal型
94
+
95
+ 1. 文頭に「数字.」を入れるとDecimal型リストになります
96
+ 2. 後からの挿入/移動を考慮して、1. 2. 3. と順番にするのではなく、1. 1. 1. という風に同じ数字にしておくといい具合です。
97
+ 3. リストを挿入する際は、 **リストの上下に空行がないと正しく表示されません。また「数字.」の後にはスペースが必要です**
98
+
99
+ ### Definition型
100
+
101
+ HTMLの`<dl>`タグをそのまま使うことで実現できます。
102
+
103
+ ```html
104
+ <dl>
105
+ <dt>リンゴ</dt>
106
+ <dd>赤いフルーツ</dd>
107
+ <dt>オレンジ</dt>
108
+ <dd>橙色のフルーツ</dd>
109
+ </dl>
110
+ ```
111
+ 次のようになります。
112
+
113
+ <dl>
114
+ <dt>リンゴ</dt>
115
+ <dd>赤いフルーツ</dd>
116
+ <dt>オレンジ</dt>
117
+ <dd>橙色フルーツ</dd>
118
+ </dl>
119
+
120
+ 注意するべきは、Definition型のリスト内ではMarkdown記法が使えないということです。例えば以下のように書いてはなりません。
121
+
122
+ ```html
123
+ <dl>
124
+ <dt>リンゴ</dt>
125
+ <dd> とても **赤い** フルーツ </dd>
126
+ </dl>
127
+ ```
128
+
129
+ 次のようになってしまいます。
130
+
131
+ <dl>
132
+ <dt>リンゴ</dt>
133
+ <dd> とても **赤い** フルーツ </dd>
134
+ </dl>
135
+
136
+ Definition型リスト内ではMarkdown記法ではなくて、HTMLタグを使って修飾しなければならないので、正しくは次のようになります。
137
+
138
+ ```html
139
+ <dl>
140
+ <dt>リンゴ</dt>
141
+ <dd> とても<strong>赤い</strong>フルーツ </dd>
142
+ </dl>
143
+ ```
144
+
145
+ <dl>
146
+ <dt>リンゴ</dt>
147
+ <dd> とても<strong>赤い</strong>フルーツ</dd>
148
+ </dl>
149
+
150
+ Markdown記法とHTMLタグの対応は次のようになっています。
151
+
152
+ | 修飾 | Markdown | HTML |
153
+ |:----------:|:---------------:|:------------------------:|
154
+ | ボールド | `** **` | `<strong></strong>` |
155
+ | イタリック | `_ _` | `<em></em>` |
156
+ | コード | <code>``</code> | `<code></code>` |
157
+ | リンク | `[text](url)` | `<a href="url">text</a>` |
158
+
159
+ ## Blockquotes - 引用
160
+
161
+ > \> 文頭に>を置くことで引用になります。
162
+ > \> 複数行にまたがる場合、改行のたびにこの記号を置く必要があります。
163
+ > \> **引用の上下にはリストと同じく空行がないと正しく表示されません**
164
+ > \> 引用の中に別のMarkdownを使用することも可能です。
165
+
166
+ > > これはネストされた引用です。
167
+
168
+ ## Horizontal rules - 水平線
169
+
170
+ 下記は全て水平線として表示されます
171
+
172
+ > \* * *
173
+ > \***
174
+ > \*****
175
+ > \- - -
176
+ > \---------------------------------------
177
+
178
+ ## Links - リンク
179
+
180
+ * \[リンクテキスト](URL "タイトル")
181
+ * タイトル付きのリンクを投稿できます。
182
+
183
+ **例**
184
+
185
+ > *Markdown:* \[Qiita]\(http://qiita.com "Qiita")
186
+ > *結果:* [Qiita](http://qiita.com "Qiita")
187
+
188
+ * \[リンクテキスト](URL)
189
+ * こちらはタイトル無しのリンクになります。
190
+
191
+ **例**
192
+
193
+ > *Markdown:* \[Qiita]\(http://qiita.com)
194
+ > *結果:* [Qiita](http://qiita.com)
195
+
196
+ - \[リンクテキスト]\[名前]
197
+ - \[名前]:URL
198
+ - 同じURLへのリンクを複数箇所に設定することができます
199
+
200
+ **例**
201
+
202
+ >
203
+ *Markdown:*
204
+ \[ここ]\[link-1] と \[この]\[link-1] リンクは同じになります。
205
+ \[link-1][\] も可能です。
206
+ \[link-1]:http://qiita.com/drafts/c686397e4a0f4f11683d
207
+ >
208
+ *結果:*
209
+ [ここ][link-1] と [この][link-1] リンクは同じになります。
210
+ [link-1][] も可能です。
211
+ [link-1]:http://qiita.com/drafts/c686397e4a0f4f11683d
212
+
213
+ ## Images - 画像埋め込み
214
+
215
+ * \![代替テキスト]\(画像のURL)
216
+ * タイトル無しの画像を埋め込む
217
+ * \![代替テキスト]\(画像のURL "画像タイトル")
218
+ * タイトル有りの画像を埋め込む
219
+
220
+ **例**
221
+
222
+ > *Markdown:* \![Qiita]\(http://qiita.com/icons/favicons/public/apple-touch-icon.png "Qiita")
223
+ > *結果:*
224
+ > ![Qiita](http://qiita.com/icons/favicons/public/apple-touch-icon.png "Qiita")
225
+
226
+ ## テーブル記法
227
+ ```
228
+ | Left align | Right align | Center align |
229
+ |:-----------|------------:|:------------:|
230
+ | This | This | This |
231
+ | column | column | column |
232
+ | will | will | will |
233
+ | be | be | be |
234
+ | left | right | center |
235
+ | aligned | aligned | aligned |
236
+ ```
237
+
238
+ 上記のように書くと,以下のように表示されます.
239
+
240
+ | Left align | Right align | Center align |
241
+ |:-----------|------------:|:------------:|
242
+ | This | This | This |
243
+ | column | column | column |
244
+ | will | will | will |
245
+ | be | be | be |
246
+ | left | right | center |
247
+ | aligned | aligned | aligned |
248
+
249
+ ## 数式の挿入
250
+
251
+ コードブロックの言語指定に "math" を指定することでTeX記法を用いて数式を記述することができます。
252
+
253
+ > \`\`\`math
254
+ > \left( \sum_{k=1}^n a_k b_k \right)^{\!\!2} \leq
255
+ > \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)
256
+ > \`\`\`
257
+
258
+ ```math
259
+ \left( \sum_{k=1}^n a_k b_k \right)^{\!\!2} \leq
260
+ \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)
261
+ ```
262
+
263
+ `$2^3$` のように数式を "$" で挟むと行中に数式を埋め込むこともできます。
264
+
265
+ > x^2 + y^2 = 1 をインライン表示すると $x^2 + y^2 = 1$ になります。
266
+
267
+ ただしインライン数式の中でコントロールシンボル(`\{`のような、バックスラッシュの後に記号が続くもの)を使うと、後述のバックスラッシュによるMarkdownのエスケープと衝突してしまいます。
268
+
269
+ ```
270
+ $a = \{1, 2, 3\}$
271
+ ```
272
+
273
+ > $a = \{1, 2, 3\}$
274
+
275
+ なので次のように二つのバックスラッシュを使います。
276
+
277
+ ```
278
+ $a = \\{1, 2, 3\\}$
279
+ ```
280
+
281
+ > $a = \\{1, 2, 3\\}$
282
+
283
+ ## 目次(TOC)の自動挿入
284
+
285
+ 目次は記事内の見出しを元に自動生成し、右上に自動挿入されます。詳細は[目次機能の紹介記事](http://blog.qiita.com/post/77055935852/qiita-toc)をご覧ください。
286
+
287
+ ## 注釈
288
+ 本文中に `[^1]` のように文字列を記述することで、脚注へのリンクを表現できます。注釈内容は、同じく本文中に `[^1]: ...` というように記述します[^1]。
289
+
290
+ [^1]: 注釈内容を記述する位置は、本文の途中でも末尾でも構いません。
291
+
292
+ ## 絵文字
293
+ 厳密には Markdown 記法の外ですが、`:` で囲って、絵文字を埋め込めます。
294
+
295
+ **例**
296
+
297
+ ```
298
+ \:kissing_closed_eyes: chu☆
299
+ ```
300
+
301
+ > \:kissing_closed_eyes: chu☆
302
+
303
+
304
+ 絵文字チートシート
305
+ http://www.emoji-cheat-sheet.com/
306
+
307
+
308
+ ## その他
309
+
310
+ バックスラッシュ[\\]をMarkdownの前に挿入することで、Markdownをエスケープ(無効化)することができます。
311
+
312
+ **例**
313
+
314
+ > \# H1
315
+ > エスケープされています
316
+
317
+ また本文では一部のHTMLタグも利用可能です。
@@ -15,8 +15,10 @@ require "qiita/markdown/filters/mention"
15
15
  require "qiita/markdown/filters/sanitize"
16
16
  require "qiita/markdown/filters/simplify"
17
17
  require "qiita/markdown/filters/syntax_highlight"
18
- require "qiita/markdown/filters/toc"
19
18
  require "qiita/markdown/filters/truncate"
19
+ require "qiita/markdown/greenmat/heading_rendering"
20
+ require "qiita/markdown/greenmat/html_renderer"
21
+ require "qiita/markdown/greenmat/html_toc_renderer"
20
22
  require "qiita/markdown/processor"
21
23
  require "qiita/markdown/summary_processor"
22
24
  require "qiita/markdown/version"
@@ -1,31 +1,27 @@
1
- require "greenmat"
2
-
3
1
  module Qiita
4
2
  module Markdown
5
3
  module Filters
6
4
  class Greenmat < HTML::Pipeline::TextFilter
7
- class << self
8
- # Memoize.
9
- # @return [Greenmat::Markdown]
10
- def renderer
11
- @renderer ||= ::Greenmat::Markdown.new(
12
- ::Greenmat::Render::HTML.new(
13
- hard_wrap: true,
14
- ),
15
- autolink: true,
16
- fenced_code_blocks: true,
17
- footnotes: true,
18
- no_intra_emphasis: true,
19
- no_mention_emphasis: true,
20
- strikethrough: true,
21
- tables: true,
22
- )
23
- end
24
- end
25
-
26
5
  # @return [Nokogiri::HTML::DocumentFragment]
27
6
  def call
28
- Nokogiri::HTML.fragment(self.class.renderer.render(@text))
7
+ Nokogiri::HTML.fragment(greenmat.render(@text))
8
+ end
9
+
10
+ private
11
+
12
+ # Memoize.
13
+ # @return [Greenmat::Markdown]
14
+ def greenmat
15
+ @renderer ||= ::Greenmat::Markdown.new(
16
+ Qiita::Markdown::Greenmat::HTMLRenderer.new(hard_wrap: true, with_toc_data: true),
17
+ autolink: true,
18
+ fenced_code_blocks: true,
19
+ footnotes: true,
20
+ no_intra_emphasis: true,
21
+ no_mention_emphasis: true,
22
+ strikethrough: true,
23
+ tables: true,
24
+ )
29
25
  end
30
26
  end
31
27
  end
@@ -0,0 +1,43 @@
1
+ module Qiita
2
+ module Markdown
3
+ module Greenmat
4
+ module HeadingRendering
5
+ def heading_counter
6
+ @counter ||= Hash.new(0)
7
+ end
8
+
9
+ AbstractHeading = Struct.new(:text, :level, :counter) do
10
+ def to_s
11
+ fail NotImplementedError
12
+ end
13
+
14
+ def increment
15
+ fail NotImplementedError
16
+ end
17
+
18
+ private
19
+
20
+ def count
21
+ counter[id]
22
+ end
23
+
24
+ def has_count?
25
+ count > 0
26
+ end
27
+
28
+ def id
29
+ @id ||= text.downcase.gsub(/[^\p{Word}\- ]/u, "").gsub(" ", "-")
30
+ end
31
+
32
+ def suffix
33
+ has_count? ? "-#{count}" : ""
34
+ end
35
+
36
+ def suffixed_id
37
+ @suffixed_id ||= "#{id}#{suffix}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,55 @@
1
+ module Qiita
2
+ module Markdown
3
+ module Greenmat
4
+ class HTMLRenderer < ::Greenmat::Render::HTML
5
+ include HeadingRendering
6
+
7
+ def initialize(extensions = {})
8
+ super
9
+ @with_toc_data = extensions[:with_toc_data]
10
+ end
11
+
12
+ def header(text, level)
13
+ heading = heading_class.new(text, level, heading_counter)
14
+ heading.to_s.tap do
15
+ heading.increment
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def heading_class
22
+ @heading_class ||= (@with_toc_data ? HeadingWithAnchor : Heading)
23
+ end
24
+
25
+ class Heading < AbstractHeading
26
+ # For reference, C implementation of Redcarpet::Render::HTML#header is the following:
27
+ # https://github.com/vmg/redcarpet/blob/v3.2.3/ext/redcarpet/html.c#L281-L296
28
+ def to_s
29
+ "\n<h#{level}>#{text}</h#{level}>\n"
30
+ end
31
+
32
+ def increment
33
+ # No-op
34
+ end
35
+ end
36
+
37
+ class HeadingWithAnchor < AbstractHeading
38
+ def to_s
39
+ "\n<h#{level}>#{anchor_element}#{text}</h#{level}>\n"
40
+ end
41
+
42
+ def increment
43
+ counter[id] += 1
44
+ end
45
+
46
+ private
47
+
48
+ def anchor_element
49
+ %(<span id="#{suffixed_id}" class="fragment"></span><a href="##{suffixed_id}"><i class="fa fa-link"></i></a>)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,67 @@
1
+ module Qiita
2
+ module Markdown
3
+ module Greenmat
4
+ class HTMLToCRenderer < ::Greenmat::Render::HTML_TOC
5
+ include HeadingRendering
6
+
7
+ def initialize(*)
8
+ super
9
+ @last_level = 0
10
+ end
11
+
12
+ # https://github.com/vmg/redcarpet/blob/v3.2.3/ext/redcarpet/html.c#L609-L642
13
+ def header(text, level)
14
+ @level_offset = level - 1 unless @level_offset
15
+
16
+ level -= @level_offset
17
+ level = 1 if level < 1
18
+
19
+ difference = level - @last_level
20
+ @last_level = level
21
+
22
+ generate_heading_html(text, level, difference)
23
+ end
24
+
25
+ # https://github.com/vmg/redcarpet/blob/v3.2.3/ext/redcarpet/html.c#L652-L661
26
+ def doc_footer
27
+ "</li>\n</ul>\n" * @last_level
28
+ end
29
+
30
+ private
31
+
32
+ def generate_heading_html(text, level, level_difference)
33
+ html = list_item_preceding_html(level_difference)
34
+
35
+ anchor = HeadingAnchor.new(text, level, heading_counter)
36
+ html << anchor.to_s
37
+ anchor.increment
38
+
39
+ html
40
+ end
41
+
42
+ def list_item_preceding_html(level_difference)
43
+ html = case
44
+ when level_difference > 0
45
+ "<ul>\n" * level_difference
46
+ when level_difference < 0
47
+ "</li>\n" << ("</ul>\n</li>\n" * level_difference.abs)
48
+ else
49
+ "</li>\n"
50
+ end
51
+
52
+ html << "<li>\n"
53
+ end
54
+
55
+ class HeadingAnchor < AbstractHeading
56
+ def to_s
57
+ "<a href=\"##{suffixed_id}\">#{text}</a>\n"
58
+ end
59
+
60
+ def increment
61
+ counter[id] += 1
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -10,7 +10,6 @@ module Qiita
10
10
  Filters::Footnote,
11
11
  Filters::Code,
12
12
  Filters::Checkbox,
13
- Filters::Toc,
14
13
  HTML::Pipeline::EmojiFilter,
15
14
  Filters::SyntaxHighlight,
16
15
  Filters::Mention,
@@ -1,5 +1,5 @@
1
1
  module Qiita
2
2
  module Markdown
3
- VERSION = "0.4.2"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  end
@@ -16,6 +16,8 @@ Gem::Specification.new do |spec|
16
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
17
  spec.require_paths = ["lib"]
18
18
 
19
+ spec.required_ruby_version = ">= 2.0.0"
20
+
19
21
  spec.add_dependency "activesupport"
20
22
  spec.add_dependency "gemoji"
21
23
  spec.add_dependency "github-linguist"
@@ -25,6 +27,7 @@ Gem::Specification.new do |spec|
25
27
  spec.add_dependency "greenmat", ">= 3.2.0.2", "< 4"
26
28
  spec.add_dependency "rugged", ">= 0.21.1b2"
27
29
  spec.add_dependency "sanitize"
30
+ spec.add_development_dependency "benchmark-ips", "~> 1.2"
28
31
  spec.add_development_dependency "bundler", "~> 1.7"
29
32
  spec.add_development_dependency "codeclimate-test-reporter", "0.4.4"
30
33
  spec.add_development_dependency "pry"
@@ -0,0 +1,53 @@
1
+ require "active_support/core_ext/string/strip"
2
+
3
+ describe Qiita::Markdown::Greenmat::HTMLRenderer do
4
+ let(:renderer) { described_class.new(extension) }
5
+ let(:extension) { {} }
6
+ let(:greenmat) { ::Greenmat::Markdown.new(renderer) }
7
+ subject(:rendered_html) { greenmat.render(markdown) }
8
+
9
+ describe "headings" do
10
+ let(:markdown) do
11
+ <<-EOS.strip_heredoc
12
+ # a
13
+ ## a
14
+ ### a
15
+ ### a
16
+ EOS
17
+ end
18
+
19
+ context "with :with_toc_data extension" do
20
+ let(:extension) { { with_toc_data: true } }
21
+
22
+ it "renders headings with ToC anchor" do
23
+ should eq <<-EOS.strip_heredoc
24
+
25
+ <h1><span id="a" class="fragment"></span><a href="#a"><i class="fa fa-link"></i></a>a</h1>
26
+
27
+ <h2><span id="a-1" class="fragment"></span><a href="#a-1"><i class="fa fa-link"></i></a>a</h2>
28
+
29
+ <h3><span id="a-2" class="fragment"></span><a href="#a-2"><i class="fa fa-link"></i></a>a</h3>
30
+
31
+ <h3><span id="a-3" class="fragment"></span><a href="#a-3"><i class="fa fa-link"></i></a>a</h3>
32
+ EOS
33
+ end
34
+ end
35
+
36
+ context "without :with_toc_data extension" do
37
+ let(:extension) { { with_toc_data: false } }
38
+
39
+ it "renders headings without ToC anchor" do
40
+ should eq <<-EOS.strip_heredoc
41
+
42
+ <h1>a</h1>
43
+
44
+ <h2>a</h2>
45
+
46
+ <h3>a</h3>
47
+
48
+ <h3>a</h3>
49
+ EOS
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,97 @@
1
+ require "active_support/core_ext/string/strip"
2
+
3
+ describe Qiita::Markdown::Greenmat::HTMLToCRenderer do
4
+ let(:renderer) { described_class.new }
5
+ let(:greenmat) { ::Greenmat::Markdown.new(renderer) }
6
+ subject(:rendered_html) { greenmat.render(markdown) }
7
+
8
+ context "with duplicated heading names" do
9
+ let(:markdown) do
10
+ <<-EOS.strip_heredoc
11
+ # a
12
+ ## a
13
+ ### a
14
+ ### a
15
+ EOS
16
+ end
17
+
18
+ it "renders ToC anchors with unique ids" do
19
+ should eq <<-EOS.strip_heredoc
20
+ <ul>
21
+ <li>
22
+ <a href="#a">a</a>
23
+ <ul>
24
+ <li>
25
+ <a href="#a-1">a</a>
26
+ <ul>
27
+ <li>
28
+ <a href="#a-2">a</a>
29
+ </li>
30
+ <li>
31
+ <a href="#a-3">a</a>
32
+ </li>
33
+ </ul>
34
+ </li>
35
+ </ul>
36
+ </li>
37
+ </ul>
38
+ EOS
39
+ end
40
+ end
41
+
42
+ context "with a document starting with level 2 heading" do
43
+ let(:markdown) do
44
+ <<-EOS.strip_heredoc
45
+ ## a
46
+ ### a
47
+ ## a
48
+ EOS
49
+ end
50
+
51
+ it "offsets the heading levels" do
52
+ should eq <<-EOS.strip_heredoc
53
+ <ul>
54
+ <li>
55
+ <a href="#a">a</a>
56
+ <ul>
57
+ <li>
58
+ <a href="#a-1">a</a>
59
+ </li>
60
+ </ul>
61
+ </li>
62
+ <li>
63
+ <a href="#a-2">a</a>
64
+ </li>
65
+ </ul>
66
+ EOS
67
+ end
68
+ end
69
+
70
+ context "with a document starting with level 2 heading but includes level 1 heading at the end" do
71
+ let(:markdown) do
72
+ <<-EOS.strip_heredoc
73
+ ## a
74
+ ### a
75
+ # a
76
+ EOS
77
+ end
78
+
79
+ it "does not generate invalid list structure" do
80
+ should eq <<-EOS.strip_heredoc
81
+ <ul>
82
+ <li>
83
+ <a href="#a">a</a>
84
+ <ul>
85
+ <li>
86
+ <a href="#a-1">a</a>
87
+ </li>
88
+ </ul>
89
+ </li>
90
+ <li>
91
+ <a href="#a-2">a</a>
92
+ </li>
93
+ </ul>
94
+ EOS
95
+ end
96
+ end
97
+ end
@@ -56,6 +56,7 @@ describe Qiita::Markdown::Processor do
56
56
 
57
57
  it "adds ID for ToC" do
58
58
  should eq <<-EOS.strip_heredoc
59
+
59
60
  <h1>
60
61
  <span id="a" class="fragment"></span><a href="#a"><i class="fa fa-link"></i></a>a</h1>
61
62
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qiita-markdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-26 00:00:00.000000000 Z
11
+ date: 2015-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -142,6 +142,20 @@ dependencies:
142
142
  - - ">="
143
143
  - !ruby/object:Gem::Version
144
144
  version: '0'
145
+ - !ruby/object:Gem::Dependency
146
+ name: benchmark-ips
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '1.2'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '1.2'
145
159
  - !ruby/object:Gem::Dependency
146
160
  name: bundler
147
161
  requirement: !ruby/object:Gem::Requirement
@@ -243,6 +257,9 @@ files:
243
257
  - LICENSE.txt
244
258
  - README.md
245
259
  - Rakefile
260
+ - benchmark/.rubocop.yml
261
+ - benchmark/heading_anchor_rendering.rb
262
+ - benchmark/sample.md
246
263
  - lib/qiita-markdown.rb
247
264
  - lib/qiita/markdown.rb
248
265
  - lib/qiita/markdown/filters/checkbox.rb
@@ -253,12 +270,16 @@ files:
253
270
  - lib/qiita/markdown/filters/sanitize.rb
254
271
  - lib/qiita/markdown/filters/simplify.rb
255
272
  - lib/qiita/markdown/filters/syntax_highlight.rb
256
- - lib/qiita/markdown/filters/toc.rb
257
273
  - lib/qiita/markdown/filters/truncate.rb
274
+ - lib/qiita/markdown/greenmat/heading_rendering.rb
275
+ - lib/qiita/markdown/greenmat/html_renderer.rb
276
+ - lib/qiita/markdown/greenmat/html_toc_renderer.rb
258
277
  - lib/qiita/markdown/processor.rb
259
278
  - lib/qiita/markdown/summary_processor.rb
260
279
  - lib/qiita/markdown/version.rb
261
280
  - qiita-markdown.gemspec
281
+ - spec/qiita/markdown/greenmat/html_renderer_spec.rb
282
+ - spec/qiita/markdown/greenmat/html_toc_renderer_spec.rb
262
283
  - spec/qiita/markdown/processor_spec.rb
263
284
  - spec/qiita/markdown/summary_processor_spec.rb
264
285
  - spec/spec_helper.rb
@@ -274,7 +295,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
274
295
  requirements:
275
296
  - - ">="
276
297
  - !ruby/object:Gem::Version
277
- version: '0'
298
+ version: 2.0.0
278
299
  required_rubygems_version: !ruby/object:Gem::Requirement
279
300
  requirements:
280
301
  - - ">="
@@ -282,11 +303,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
282
303
  version: '0'
283
304
  requirements: []
284
305
  rubyforge_project:
285
- rubygems_version: 2.4.5
306
+ rubygems_version: 2.4.6
286
307
  signing_key:
287
308
  specification_version: 4
288
309
  summary: Qiita-specified markdown processor.
289
310
  test_files:
311
+ - spec/qiita/markdown/greenmat/html_renderer_spec.rb
312
+ - spec/qiita/markdown/greenmat/html_toc_renderer_spec.rb
290
313
  - spec/qiita/markdown/processor_spec.rb
291
314
  - spec/qiita/markdown/summary_processor_spec.rb
292
315
  - spec/spec_helper.rb
@@ -1,68 +0,0 @@
1
- module Qiita
2
- module Markdown
3
- module Filters
4
- class Toc < HTML::Pipeline::Filter
5
- def call
6
- counter = Hash.new(0)
7
- doc.css("h1, h2, h3, h4, h5, h6").each do |node|
8
- heading = Heading.new(node, counter)
9
- heading.add_anchor_element if heading.has_first_child?
10
- heading.increment
11
- end
12
- doc
13
- end
14
-
15
- class Heading
16
- def initialize(node, counter)
17
- @node = node
18
- @counter = counter
19
- end
20
-
21
- def add_anchor_element
22
- first_child.add_previous_sibling(anchor_element)
23
- end
24
-
25
- def anchor_element
26
- %[<span id="#{suffixed_id}" class="fragment"></span><a href="##{suffixed_id}"><i class="fa fa-link"></i></a>]
27
- end
28
-
29
- def content
30
- @content ||= node.children.first
31
- end
32
-
33
- def count
34
- @counter[id]
35
- end
36
-
37
- def first_child
38
- @first_child ||= @node.children.first
39
- end
40
-
41
- def has_count?
42
- count > 0
43
- end
44
-
45
- def has_first_child?
46
- !!first_child
47
- end
48
-
49
- def id
50
- @node.text.downcase.gsub(/[^\p{Word}\- ]/u, "").gsub(" ", "-")
51
- end
52
-
53
- def increment
54
- @counter[id] += 1
55
- end
56
-
57
- def suffix
58
- has_count? ? "-#{count}" : ""
59
- end
60
-
61
- def suffixed_id
62
- "#{id}#{suffix}"
63
- end
64
- end
65
- end
66
- end
67
- end
68
- end