slodown 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/{LICENSE.txt → LICENSE} +0 -0
- data/README.md +39 -3
- data/lib/kramdown/converter/slodown_html.rb +15 -0
- data/lib/slodown.rb +6 -96
- data/lib/slodown/embed_transformer.rb +26 -0
- data/lib/slodown/formatter.rb +72 -0
- data/lib/slodown/version.rb +1 -1
- data/slodown.gemspec +3 -2
- metadata +25 -4
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
-
# Slodown
|
1
|
+
# Slodown: the ultimate user input rendering pipeline.
|
2
2
|
|
3
|
-
|
3
|
+
I love Markdown. I love syntax highlighting. I love oEmbed. And last but not least, I love whitelist-based HTML sanitizing. **Slodown** rolls all of these into one, and then some.
|
4
|
+
|
5
|
+
Here's what Slodown does by default:
|
6
|
+
|
7
|
+
- **render extended Markdown into HTML**. It uses the [kramdown](http://kramdown.rubyforge.org/) library, so yes, footnotes are supported!
|
8
|
+
- **supports super-easy rich media embeds**, [sloblog.io-style](http://sloblog.io/~hmans/qhdsk2SMoAU). Just point the Markdown image syntax at, say, a Youtube video, and Slodown will fetch the complete embed code through the magic of [ruby-oembed](https://github.com/judofyr/ruby-oembed).
|
9
|
+
- **auto-link contained URLs** using [Rinku](https://github.com/vmg/rinku), which is smart enough to not auto-link URLs contained in, say, code blocks.
|
10
|
+
- **sanitize the generated HTML** using the white-list based [sanitize](https://github.com/rgrove/sanitize) gem.
|
4
11
|
|
5
12
|
## Installation
|
6
13
|
|
@@ -18,7 +25,36 @@ Or install it yourself as:
|
|
18
25
|
|
19
26
|
## Usage
|
20
27
|
|
21
|
-
|
28
|
+
For every piece of user input that needs to be rendered, create an instance of `Slodown::Formatter` with the source text and use it to perform somre or all transformations on it. Finally, call `#to_s` to get the rendered output.
|
29
|
+
|
30
|
+
### Examples:
|
31
|
+
|
32
|
+
~~~ruby
|
33
|
+
# let's create an instance to work with
|
34
|
+
formatter = Slodown::Formatter.new(text)
|
35
|
+
|
36
|
+
# just markdown
|
37
|
+
@formatter.markdown.to_s
|
38
|
+
|
39
|
+
# just HTML tag sanitizing
|
40
|
+
@formatter.sanitize.to_s
|
41
|
+
|
42
|
+
# you can chain multiple operations
|
43
|
+
@formatter.markdown.sanitize.to_s
|
44
|
+
|
45
|
+
# this is the whole deal:
|
46
|
+
@formatter.markdown.autolink.sanitize.to_s
|
47
|
+
|
48
|
+
# which is the same as:
|
49
|
+
@formatter.complete.to_s
|
50
|
+
~~~
|
51
|
+
|
52
|
+
## Hints
|
53
|
+
|
54
|
+
* If you want to add more transformations or change the behavior of the `#complete` method, just subclass `Slodown::Formatter` and go wild. :-)
|
55
|
+
* Markdown transformations, HTML sanitizing, oEmbed handshakes and other operations are pretty expensive operations. For sake of performance (and stability), it is recommended that you cache the generated output in some manner.
|
56
|
+
* Eat more Schnitzel.
|
57
|
+
|
22
58
|
|
23
59
|
## Contributing
|
24
60
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# This is the custom kramdown converter class we will be using to render
|
2
|
+
# HTML from Markdown. It's essentially a handy way to hook into various
|
3
|
+
# elements and add our own logic (like supporting oEmbed embeds in
|
4
|
+
# Markdown image elements.)
|
5
|
+
#
|
6
|
+
class Kramdown::Converter::SlodownHtml < Kramdown::Converter::Html
|
7
|
+
# Hook into image tags to allow oEmbed embeds.
|
8
|
+
#
|
9
|
+
def convert_img(el, indent)
|
10
|
+
oembed = OEmbed::Providers.get(el.attr['src'])
|
11
|
+
%q(<div class="embedded %s %s">%s</div>) % [oembed.type, oembed.provider_name.parameterize, oembed.html]
|
12
|
+
rescue OEmbed::NotFound
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
data/lib/slodown.rb
CHANGED
@@ -4,105 +4,15 @@ require 'kramdown'
|
|
4
4
|
require 'coderay'
|
5
5
|
require 'sanitize'
|
6
6
|
|
7
|
+
require "kramdown/converter/slodown_html"
|
7
8
|
require "slodown/version"
|
9
|
+
require "slodown/formatter"
|
10
|
+
require "slodown/embed_transformer"
|
8
11
|
|
12
|
+
# Register all known oEmbed providers.
|
13
|
+
#
|
9
14
|
OEmbed::Providers.register_all
|
10
15
|
|
11
|
-
class Kramdown::Converter::SloblogHtml < Kramdown::Converter::Html
|
12
|
-
def convert_img(el, indent)
|
13
|
-
oembed = OEmbed::Providers.get(el.attr['src'])
|
14
|
-
%q(<div class="embedded %s %s">%s</div>) % [oembed.type, oembed.provider_name.parameterize, oembed.html]
|
15
|
-
rescue OEmbed::NotFound
|
16
|
-
super
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
16
|
module Slodown
|
21
|
-
|
22
|
-
ALLOWED_DOMAINS = %w[youtube.com soundcloud.com vimeo.com]
|
23
|
-
|
24
|
-
def self.call(env)
|
25
|
-
node = env[:node]
|
26
|
-
node_name = env[:node_name]
|
27
|
-
|
28
|
-
return if env[:is_whitelisted] || !env[:node].element?
|
29
|
-
return unless %w[iframe embed].include? env[:node_name]
|
30
|
-
|
31
|
-
uri = URI(env[:node]['src'])
|
32
|
-
domains = ALLOWED_DOMAINS.map { |d| Regexp.escape(d) }.join("|")
|
33
|
-
return unless uri.host =~ /^(.+\.)?(#{domains})/
|
34
|
-
|
35
|
-
Sanitize.clean_node!(node, {
|
36
|
-
elements: %w[iframe embed],
|
37
|
-
attributes: {
|
38
|
-
all: %w[allowfullscreen frameborder height src width]
|
39
|
-
}
|
40
|
-
})
|
41
|
-
|
42
|
-
{ node_whitelist: [node] }
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class Formatter
|
47
|
-
def initialize(source)
|
48
|
-
@current = @source = source.to_s
|
49
|
-
end
|
50
|
-
|
51
|
-
def complete
|
52
|
-
markdown.autolink.sanitize
|
53
|
-
end
|
54
|
-
|
55
|
-
def markdown
|
56
|
-
@current = Kramdown::Document.new(@current).to_sloblog_html
|
57
|
-
self
|
58
|
-
end
|
59
|
-
|
60
|
-
def autolink
|
61
|
-
@current = Rinku.auto_link(@current)
|
62
|
-
self
|
63
|
-
end
|
64
|
-
|
65
|
-
def sanitize(mode = :normal)
|
66
|
-
@current = case mode
|
67
|
-
when :normal
|
68
|
-
Sanitize.clean(@current,
|
69
|
-
elements: %w(
|
70
|
-
p a span sub sup strong em div hr abbr
|
71
|
-
ul ol li
|
72
|
-
blockquote pre code
|
73
|
-
h1 h2 h3 h4 h5 h6
|
74
|
-
img object param del
|
75
|
-
),
|
76
|
-
attributes: {
|
77
|
-
:all => ['class', 'style', 'title'],
|
78
|
-
'a' => ['href', 'rel', 'name'],
|
79
|
-
'li' => ['id'],
|
80
|
-
'sup' => ['id'],
|
81
|
-
'img' => ['src', 'title', 'alt', 'width', 'height'],
|
82
|
-
'object' => ['width', 'height'],
|
83
|
-
'param' => ['name', 'value'],
|
84
|
-
'embed' => ['allowscriptaccess', 'width', 'height', 'src'],
|
85
|
-
'iframe' => ['width', 'height', 'src']
|
86
|
-
},
|
87
|
-
protocols: {
|
88
|
-
'a' => { 'href' => ['ftp', 'http', 'https', 'mailto', '#fn', '#fnref', :relative] },
|
89
|
-
'img' => {'src' => ['http', 'https', :relative]},
|
90
|
-
'iframe' => {'src' => ['http', 'https']},
|
91
|
-
'embed' => {'src' => ['http', 'https']},
|
92
|
-
'object' => {'src' => ['http', 'https']},
|
93
|
-
'li' => {'id' => ['fn']},
|
94
|
-
'sup' => {'id' => ['fnref']}
|
95
|
-
},
|
96
|
-
transformers: EmbedTransformer)
|
97
|
-
else
|
98
|
-
Sanitize.clean(@current)
|
99
|
-
end
|
100
|
-
|
101
|
-
self
|
102
|
-
end
|
103
|
-
|
104
|
-
def to_s
|
105
|
-
@current
|
106
|
-
end
|
107
|
-
end
|
17
|
+
# Our main module. Not much happening here. I like huskies.
|
108
18
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Slodown
|
2
|
+
class EmbedTransformer
|
3
|
+
ALLOWED_DOMAINS = %w[youtube.com soundcloud.com vimeo.com]
|
4
|
+
|
5
|
+
def self.call(env)
|
6
|
+
node = env[:node]
|
7
|
+
node_name = env[:node_name]
|
8
|
+
|
9
|
+
return if env[:is_whitelisted] || !env[:node].element?
|
10
|
+
return unless %w[iframe embed].include? env[:node_name]
|
11
|
+
|
12
|
+
uri = URI(env[:node]['src'])
|
13
|
+
domains = ALLOWED_DOMAINS.map { |d| Regexp.escape(d) }.join("|")
|
14
|
+
return unless uri.host =~ /^(.+\.)?(#{domains})/
|
15
|
+
|
16
|
+
Sanitize.clean_node!(node, {
|
17
|
+
elements: %w[iframe embed],
|
18
|
+
attributes: {
|
19
|
+
all: %w[allowfullscreen frameborder height src width]
|
20
|
+
}
|
21
|
+
})
|
22
|
+
|
23
|
+
{ node_whitelist: [node] }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Slodown
|
2
|
+
class Formatter
|
3
|
+
def initialize(source)
|
4
|
+
@current = @source = source.to_s
|
5
|
+
end
|
6
|
+
|
7
|
+
# Runs the entire pipeline.
|
8
|
+
#
|
9
|
+
def complete
|
10
|
+
markdown.autolink.sanitize
|
11
|
+
end
|
12
|
+
|
13
|
+
# Convert the current document state from Markdown into HTML.
|
14
|
+
#
|
15
|
+
def markdown
|
16
|
+
@current = Kramdown::Document.new(@current).to_slodown_html
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
# Auto-link URLs through Rinku.
|
21
|
+
#
|
22
|
+
def autolink
|
23
|
+
@current = Rinku.auto_link(@current)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sanitize HTML tags.
|
28
|
+
#
|
29
|
+
def sanitize(mode = :normal)
|
30
|
+
@current = case mode
|
31
|
+
when :normal
|
32
|
+
Sanitize.clean(@current,
|
33
|
+
elements: %w(
|
34
|
+
p a span sub sup strong em div hr abbr
|
35
|
+
ul ol li
|
36
|
+
blockquote pre code
|
37
|
+
h1 h2 h3 h4 h5 h6
|
38
|
+
img object param del
|
39
|
+
),
|
40
|
+
attributes: {
|
41
|
+
:all => ['class', 'style', 'title'],
|
42
|
+
'a' => ['href', 'rel', 'name'],
|
43
|
+
'li' => ['id'],
|
44
|
+
'sup' => ['id'],
|
45
|
+
'img' => ['src', 'title', 'alt', 'width', 'height'],
|
46
|
+
'object' => ['width', 'height'],
|
47
|
+
'param' => ['name', 'value'],
|
48
|
+
'embed' => ['allowscriptaccess', 'width', 'height', 'src'],
|
49
|
+
'iframe' => ['width', 'height', 'src']
|
50
|
+
},
|
51
|
+
protocols: {
|
52
|
+
'a' => { 'href' => ['ftp', 'http', 'https', 'mailto', '#fn', '#fnref', :relative] },
|
53
|
+
'img' => {'src' => ['http', 'https', :relative]},
|
54
|
+
'iframe' => {'src' => ['http', 'https']},
|
55
|
+
'embed' => {'src' => ['http', 'https']},
|
56
|
+
'object' => {'src' => ['http', 'https']},
|
57
|
+
'li' => {'id' => ['fn']},
|
58
|
+
'sup' => {'id' => ['fnref']}
|
59
|
+
},
|
60
|
+
transformers: EmbedTransformer)
|
61
|
+
else
|
62
|
+
Sanitize.clean(@current)
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
@current
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/slodown/version.rb
CHANGED
data/slodown.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = Slodown::VERSION
|
9
9
|
gem.authors = ["Hendrik Mans"]
|
10
10
|
gem.email = ["hendrik@mans.de"]
|
11
|
-
gem.description = %q{Markdown
|
12
|
-
gem.summary = %q{Markdown
|
11
|
+
gem.description = %q{Markdown + oEmbed + Sanitize + CodeRay = the ultimate user input rendering pipeline.}
|
12
|
+
gem.summary = %q{Markdown + oEmbed + Sanitize + CodeRay = the ultimate user input rendering pipeline.}
|
13
13
|
gem.homepage = "http://github.com/hmans/slodown"
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
@@ -25,4 +25,5 @@ Gem::Specification.new do |gem|
|
|
25
25
|
|
26
26
|
gem.add_development_dependency 'rspec', '>= 2.12.0'
|
27
27
|
gem.add_development_dependency 'rspec-html-matchers'
|
28
|
+
gem.add_development_dependency 'rake'
|
28
29
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slodown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -123,7 +123,24 @@ dependencies:
|
|
123
123
|
- - ! '>='
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '0'
|
126
|
-
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rake
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
description: Markdown + oEmbed + Sanitize + CodeRay = the ultimate user input rendering
|
143
|
+
pipeline.
|
127
144
|
email:
|
128
145
|
- hendrik@mans.de
|
129
146
|
executables: []
|
@@ -134,10 +151,13 @@ files:
|
|
134
151
|
- .rspec
|
135
152
|
- .travis.yml
|
136
153
|
- Gemfile
|
137
|
-
- LICENSE
|
154
|
+
- LICENSE
|
138
155
|
- README.md
|
139
156
|
- Rakefile
|
157
|
+
- lib/kramdown/converter/slodown_html.rb
|
140
158
|
- lib/slodown.rb
|
159
|
+
- lib/slodown/embed_transformer.rb
|
160
|
+
- lib/slodown/formatter.rb
|
141
161
|
- lib/slodown/version.rb
|
142
162
|
- slodown.gemspec
|
143
163
|
- spec/basic_formatting_spec.rb
|
@@ -165,7 +185,8 @@ rubyforge_project:
|
|
165
185
|
rubygems_version: 1.8.23
|
166
186
|
signing_key:
|
167
187
|
specification_version: 3
|
168
|
-
summary: Markdown
|
188
|
+
summary: Markdown + oEmbed + Sanitize + CodeRay = the ultimate user input rendering
|
189
|
+
pipeline.
|
169
190
|
test_files:
|
170
191
|
- spec/basic_formatting_spec.rb
|
171
192
|
- spec/spec_helper.rb
|