auto_html 1.6.2 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +65 -94
- data/Rakefile +8 -7
- data/lib/auto_html/emoji.rb +54 -0
- data/lib/auto_html/html_escape.rb +12 -0
- data/lib/auto_html/image.rb +23 -0
- data/lib/auto_html/link.rb +33 -0
- data/lib/auto_html/markdown.rb +17 -0
- data/lib/auto_html/pipeline.rb +18 -0
- data/lib/auto_html/simple_format.rb +23 -0
- data/lib/auto_html/tag_helper.rb +38 -0
- data/lib/auto_html.rb +11 -15
- data/spec/auto_html/emoji_spec.rb +12 -0
- data/spec/auto_html/html_escape_spec.rb +10 -0
- data/spec/auto_html/image_spec.rb +41 -0
- data/spec/auto_html/link_spec.rb +53 -0
- data/spec/auto_html/markdown_spec.rb +9 -0
- data/spec/auto_html/pipeline_spec.rb +26 -0
- data/spec/auto_html/simple_format_spec.rb +18 -0
- data/spec/auto_html/tag_helper_spec.rb +30 -0
- data/spec/spec_helper.rb +13 -0
- metadata +114 -90
- data/lib/auto_html/auto_html_for.rb +0 -64
- data/lib/auto_html/base.rb +0 -18
- data/lib/auto_html/builder.rb +0 -21
- data/lib/auto_html/capistrano.rb +0 -17
- data/lib/auto_html/filter.rb +0 -22
- data/lib/auto_html/filters/dailymotion.rb +0 -6
- data/lib/auto_html/filters/flickr.rb +0 -20
- data/lib/auto_html/filters/gist.rb +0 -8
- data/lib/auto_html/filters/google_map.rb +0 -19
- data/lib/auto_html/filters/google_video.rb +0 -6
- data/lib/auto_html/filters/hashtag.rb +0 -7
- data/lib/auto_html/filters/html_escape.rb +0 -9
- data/lib/auto_html/filters/image.rb +0 -16
- data/lib/auto_html/filters/instagram.rb +0 -10
- data/lib/auto_html/filters/link.rb +0 -16
- data/lib/auto_html/filters/metacafe.rb +0 -13
- data/lib/auto_html/filters/redcarpet.rb +0 -4
- data/lib/auto_html/filters/sanitize.rb +0 -5
- data/lib/auto_html/filters/simple_format.rb +0 -12
- data/lib/auto_html/filters/soundcloud.rb +0 -21
- data/lib/auto_html/filters/ted.rb +0 -13
- data/lib/auto_html/filters/twitter.rb +0 -16
- data/lib/auto_html/filters/vimeo.rb +0 -15
- data/lib/auto_html/filters/worldstar.rb +0 -8
- data/lib/auto_html/filters/youtube.rb +0 -19
- data/lib/auto_html/filters/youtube_js_api.rb +0 -6
- data/lib/auto_html/railtie.rb +0 -10
- data/lib/auto_html/rake_tasks.rb +0 -27
- data/lib/auto_html/task.rb +0 -9
- data/test/fixture_setup.rb +0 -13
- data/test/fixtures/database.yml +0 -5
- data/test/fixtures/schema.rb +0 -20
- data/test/functional/auto_html_for_options_test.rb +0 -26
- data/test/functional/auto_html_for_test.rb +0 -56
- data/test/functional/filter_test.rb +0 -27
- data/test/test_helper.rb +0 -9
- data/test/unit/auto_html_test.rb +0 -37
- data/test/unit/filters/dailymotion_test.rb +0 -20
- data/test/unit/filters/gist_test.rb +0 -15
- data/test/unit/filters/google_map_test.rb +0 -24
- data/test/unit/filters/hashtag_test.rb +0 -25
- data/test/unit/filters/html_escape_test.rb +0 -15
- data/test/unit/filters/image_test.rb +0 -65
- data/test/unit/filters/instagram_test.rb +0 -8
- data/test/unit/filters/link_test.rb +0 -55
- data/test/unit/filters/metacafe_test.rb +0 -29
- data/test/unit/filters/redcarpet_test.rb +0 -38
- data/test/unit/filters/sanitize_test.rb +0 -36
- data/test/unit/filters/simple_format_test.rb +0 -15
- data/test/unit/filters/soundcloud_test.rb +0 -29
- data/test/unit/filters/ted_test.rb +0 -18
- data/test/unit/filters/twitter_test.rb +0 -40
- data/test/unit/filters/vimeo_test.rb +0 -50
- data/test/unit/filters/worldstar_test.rb +0 -14
- data/test/unit/filters/youtube_js_api_test.rb +0 -30
- data/test/unit/filters/youtube_test.rb +0 -59
- data/test/unit/unit_test_helper.rb +0 -3
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f0bf51223467c7efbbf7f743e446137634deb7370ecbf73cedbfb8d434bb04f8
|
4
|
+
data.tar.gz: e5a1b45c89fdc951906e7e80ce9a9c863edeeb0cf7a5707afe2e1b9bffa96292
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 85b635283440e12428cfd5ad9e42fc12a90224a433c3cf31222d7321bfc4bdaffa3c39ace46bfdda54ca87808cae8e4a4a8442694c1f30666469c3017b9cb252
|
7
|
+
data.tar.gz: '08720ba6a147ab68def101baddf20c7a867ba3edaea3a13ccc6de3ee99e05f34a02f01d10c98d7a8a0e851933af6b117dce774341657002b1a59a34c9dddedff'
|
data/README.md
CHANGED
@@ -1,123 +1,94 @@
|
|
1
|
-
|
2
|
-
=========
|
1
|
+
# AutoHtml
|
3
2
|
|
3
|
+
AutoHtml is a collection of filters that transforms plain text into HTML code.
|
4
4
|
|
5
|
-
|
5
|
+
## Installation
|
6
6
|
|
7
|
+
Add this line to your application's Gemfile:
|
7
8
|
|
8
|
-
|
9
|
+
```ruby
|
10
|
+
gem 'auto_html'
|
11
|
+
```
|
9
12
|
|
10
|
-
|
13
|
+
And then execute:
|
11
14
|
|
12
|
-
|
15
|
+
```sh
|
16
|
+
$ bundle
|
17
|
+
```
|
13
18
|
|
19
|
+
Or install it yourself as:
|
14
20
|
|
15
|
-
|
21
|
+
```sh
|
22
|
+
$ gem install auto_html
|
23
|
+
```
|
16
24
|
|
17
|
-
|
25
|
+
## Abstract
|
18
26
|
|
19
|
-
|
20
|
-
|
21
|
-
auto_html('Hey! Checkout out: http://vukajlija.com') { simple_format; link(:target => 'blank') }
|
22
|
-
=> "<p>Hey! Checkout out: <a href='http://vukajlija.com' target='blank'>http://vukajlija.com</a></p>"
|
27
|
+
AutoHtml uses concepts found in "Pipes and Filters" processing design pattern:
|
23
28
|
|
24
|
-
|
29
|
+
* `Filter` - transforms an input. In AutoHtml context, this is any object that does the transformation through `#call(String)` method. Filter options should be passed in initializer. AutoHtml provides some filters already, ie Link, Image, Markdown, etc.
|
30
|
+
* `Pipeline` - a composition of filters that transforms input by passing the output of one filter as input for the next filter in line. In AutoHtml context, this is the `AutoHtml::Pipeline` class. Since the same interface (method `#call`) is used to pass input, we can say that Pipeline is just another Filter, which means it can be used as a building block for other Pipelines, in a mix with other filters.
|
25
31
|
|
26
|
-
|
27
|
-
auto_html_for :body do
|
28
|
-
html_escape
|
29
|
-
image
|
30
|
-
youtube(:width => 400, :height => 250, :autoplay => true)
|
31
|
-
link :target => "_blank", :rel => "nofollow"
|
32
|
-
simple_format
|
33
|
-
end
|
34
|
-
end
|
32
|
+
## Examples
|
35
33
|
|
36
|
-
|
34
|
+
```ruby
|
35
|
+
link_filter = AutoHtml::Link.new(target: '_blank')
|
36
|
+
link_filter.call('Checkout out my blog: http://rors.org')
|
37
|
+
# => 'Checkout out my blog: <a target="blank" href="http://rors.org">http://rors.org</a>'
|
37
38
|
|
38
|
-
|
39
|
-
|
39
|
+
emoji_filter = AutoHtml::Emoji.new
|
40
|
+
emoji_filter.call(':point_left: yo!')
|
41
|
+
# => '<img src="/images/emoji/unicode/1f448.png" class="emoji" title=":point_left:" alt=":point_left:" height="20" witdh="20" align="absmiddle" /> yo!'
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
<% for comment in @comments %>
|
47
|
-
<li><%= comment.body_html %></li>
|
48
|
-
<% end %>
|
49
|
-
|
50
|
-
|
51
|
-
If you need to display preview, no problem. Have something like this as action in your controller:
|
52
|
-
|
53
|
-
def preview
|
54
|
-
comment = Comment.new(params[:comment])
|
55
|
-
render :text => comment.body_html
|
56
|
-
end
|
57
|
-
|
58
|
-
AutoHtml is highly customizable, and you can easily create new filters that will transform user input any way you like. For instance, this is the image filter that comes bundled with plugin:
|
59
|
-
|
60
|
-
AutoHtml.add_filter(:image) do |text|
|
61
|
-
text.gsub(/http:\/\/.+\.(jpg|jpeg|bmp|gif|png)(\?\S+)?/i) do |match|
|
62
|
-
%|<img src="#{match}" alt=""/>|
|
63
|
-
end
|
64
|
-
end
|
43
|
+
# Use Pipeline to combine filters
|
44
|
+
base_format = AutoHtml::Pipeline.new(link_filter, emoji_filter)
|
45
|
+
base_format.call('Checkout out my blog: http://rors.org :point_left: yo!')
|
46
|
+
# => 'Checkout out my blog: <a href="http://rors.org">http://rors.org</a> <img src="/images/emoji/unicode/1f448.png" class="emoji" title=":point_left:" alt=":point_left:" height="20" witdh="20" align="absmiddle" /> yo!'
|
65
47
|
|
48
|
+
# A pipeline can be reused in another pipeline. Note that the order of filters is important - ie you want
|
49
|
+
# `Image` before `Link` filter so that URL of the image gets transformed to `img` tag and not `a` tag.
|
50
|
+
comment_format = AutoHtml::Pipeline.new(AutoHtml::Markdown.new, AutoHtml::Image.new, base_format)
|
51
|
+
comment_format.call("Hello!\n\n Checkout out my blog: http://rors.org :point_left: yo! \n\n http://gifs.joelglovier.com/boom/booyah.gif")
|
52
|
+
# => "<p>Hello!</p>\n\n<p>Checkout out my blog: <a href="<img src="http://rors.org" target="_blank">http://rors.org</a> <img src="/images/emoji/unicode/1f448.png" />" class="emoji" title=":point_left:" alt=":point_left:" height="20" witdh="20" align="absmiddle" /> yo! </p>\n\n<p><a href="<img src="http://gifs.joelglovier.com/boom/booyah.gif" />" target="_blank"><img src="http://gifs.joelglovier.com/boom/booyah.gif" /></a></p>\n"
|
53
|
+
```
|
66
54
|
|
67
55
|
## Bundled filters
|
68
56
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
## Non-ActiveRecord models
|
73
|
-
|
74
|
-
AutoHtml uses standard ActiveModel API, which means that you can include AutoHtmlFor module (that automates transformation of the field) in any non-ActiveRecord model that uses ActiveModel. Here's working [mongoid](http://mongoid.org/) example:
|
75
|
-
|
76
|
-
class Post
|
77
|
-
include Mongoid::Document
|
78
|
-
include AutoHtmlFor
|
79
|
-
|
80
|
-
field :body
|
57
|
+
Bellow is the list of bundled filters along with their optional arguments on initialization and their default values.
|
81
58
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
59
|
+
* `AutoHtml::Emoji`, width: 20, height: 20, asset_root: '/images'
|
60
|
+
* `AutoHtml::HtmlEscape`
|
61
|
+
* `AutoHtml::Image`, proxy: nil, alt: nil
|
62
|
+
* `AutoHtml::Link`, target: nil, rel: nil
|
63
|
+
* `AutoHtml::Markdown`
|
64
|
+
* `AutoHtml::SimpleFormat`
|
87
65
|
|
66
|
+
## Using AutoHtml with ActiveRecord
|
88
67
|
|
89
|
-
|
68
|
+
For performance reasons it's a good idea to store the formated output in the database, in a separate column, to avoid generating the same content on each access.
|
69
|
+
This can be acomplished simply by overriding the attribute writter:
|
90
70
|
|
91
|
-
|
92
|
-
|
93
|
-
|
71
|
+
```ruby
|
72
|
+
class Comment < ActiveRecord::Base
|
73
|
+
FORMAT = AutoHtml::Pipeline.new(
|
74
|
+
AutoHtml::HtmlEscape.new,
|
75
|
+
AutoHtml::Markdown.new
|
76
|
+
)
|
94
77
|
|
95
|
-
|
78
|
+
def text=(t)
|
79
|
+
super(t)
|
80
|
+
self[:text_html] = FORMAT.call(t)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
96
84
|
|
97
|
-
|
98
|
-
|
99
|
-
Now you can run `cap auto_html:rebuild CLASS=[your_model]`.
|
85
|
+
Now, every time `text` attribute is set, `text_html` will be set as well:
|
100
86
|
|
87
|
+
```Ruby
|
88
|
+
comment = Comment.new(text: 'Hey!')
|
89
|
+
comment.text_html # => '<p>Hey!</p>'
|
90
|
+
```
|
101
91
|
|
102
92
|
## Licence
|
103
93
|
|
104
|
-
|
105
|
-
|
106
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
107
|
-
a copy of this software and associated documentation files (the
|
108
|
-
"Software"), to deal in the Software without restriction, including
|
109
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
110
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
111
|
-
permit persons to whom the Software is furnished to do so, subject to
|
112
|
-
the following conditions:
|
113
|
-
|
114
|
-
The above copyright notice and this permission notice shall be
|
115
|
-
included in all copies or substantial portions of the Software.
|
116
|
-
|
117
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
118
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
119
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
120
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
121
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
122
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
123
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
94
|
+
AutoHtml is released under the [MIT License](https://raw.githubusercontent.com/dejan/auto_html/master/MIT-LICENSE).
|
data/Rakefile
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rubocop/rake_task'
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
RuboCop::RakeTask.new
|
9
|
+
|
10
|
+
task default: %i[rubocop spec]
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'gemoji'
|
4
|
+
|
5
|
+
module AutoHtml
|
6
|
+
# Emoji filter
|
7
|
+
class Emoji
|
8
|
+
def initialize(asset_root: '/images', width: 20, height: 20)
|
9
|
+
@asset_root = asset_root
|
10
|
+
@width = width
|
11
|
+
@height = height
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(text)
|
15
|
+
text.gsub(self.class.emoji_pattern) do
|
16
|
+
name = Regexp.last_match(1)
|
17
|
+
alt = ":#{name}:"
|
18
|
+
html_options = {
|
19
|
+
src: emoji_url(name),
|
20
|
+
class: 'emoji',
|
21
|
+
title: alt,
|
22
|
+
alt: alt,
|
23
|
+
height: @width,
|
24
|
+
witdh: @height,
|
25
|
+
align: 'absmiddle'
|
26
|
+
}
|
27
|
+
TagHelper.tag(:img, html_options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.emoji_pattern
|
32
|
+
@emoji_pattern ||=
|
33
|
+
/:(#{emoji_names.map { |name| Regexp.escape(name) }.join('|')}):/
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.emoji_names
|
37
|
+
::Emoji.all.map(&:aliases).flatten.sort
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def emoji_url(name)
|
43
|
+
File.join(@asset_root, asset_path(name))
|
44
|
+
end
|
45
|
+
|
46
|
+
def asset_path(name)
|
47
|
+
File.join('emoji', emoji_filename(name))
|
48
|
+
end
|
49
|
+
|
50
|
+
def emoji_filename(name)
|
51
|
+
::Emoji.find_by_alias(name).image_filename
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutoHtml
|
4
|
+
# Image filter
|
5
|
+
class Image
|
6
|
+
def initialize(proxy: nil, alt: nil)
|
7
|
+
@proxy = proxy || ''
|
8
|
+
@alt = alt
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(text)
|
12
|
+
text.gsub(image_pattern) do |match|
|
13
|
+
TagHelper.tag(:img, src: @proxy + match, alt: @alt)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def image_pattern
|
20
|
+
%r{(?<!src=")https?://.+?\.(jpg|jpeg|bmp|gif|png)(\?\S+)?}i
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'rinku'
|
5
|
+
require 'rexml/document'
|
6
|
+
|
7
|
+
module AutoHtml
|
8
|
+
# Link filter
|
9
|
+
class Link
|
10
|
+
def initialize(target: nil, rel: nil)
|
11
|
+
@target = target
|
12
|
+
@rel = rel
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(text)
|
16
|
+
Rinku.auto_link(text, :all, attributes)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def attributes
|
22
|
+
[target_attr, rel_attr].compact.join(' ') unless [target_attr, rel_attr].compact.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def rel_attr
|
26
|
+
%(rel="#{@rel}") if @rel
|
27
|
+
end
|
28
|
+
|
29
|
+
def target_attr
|
30
|
+
%(target="#{@target}") if @target
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redcarpet'
|
4
|
+
|
5
|
+
module AutoHtml
|
6
|
+
# Markdown filter
|
7
|
+
class Markdown
|
8
|
+
def initialize
|
9
|
+
render = Redcarpet::Render::HTML
|
10
|
+
@markdown = Redcarpet::Markdown.new(render)
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(text)
|
14
|
+
@markdown.render(text)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutoHtml
|
4
|
+
# Applies collection of filters to a text
|
5
|
+
class Pipeline
|
6
|
+
def initialize(*filters)
|
7
|
+
@filters = filters.flatten
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(text)
|
11
|
+
return '' if text.nil? || text.empty?
|
12
|
+
|
13
|
+
@filters.inject(text) do |content, filter|
|
14
|
+
filter.call(content)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutoHtml
|
4
|
+
# SimpleFormat filter
|
5
|
+
class SimpleFormat
|
6
|
+
def call(text)
|
7
|
+
paragraphs = split_paragraphs(text)
|
8
|
+
paragraphs.map! do |paragraph|
|
9
|
+
TagHelper.tag(:p) { paragraph }
|
10
|
+
end.join("\n\n")
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def split_paragraphs(text)
|
16
|
+
return [] if text.nil? || text.empty?
|
17
|
+
|
18
|
+
text.to_s.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t|
|
19
|
+
t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutoHtml
|
4
|
+
# XHTML tags builder
|
5
|
+
module TagHelper
|
6
|
+
def tag(tag_name, attrs = {})
|
7
|
+
if block_given?
|
8
|
+
content_tag(tag_name, yield, attrs)
|
9
|
+
else
|
10
|
+
unary_tag(tag_name, attrs)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def unary_tag(tag_name, attrs = {})
|
17
|
+
"<#{tag_and_attributes(tag_name, tag_attributes(attrs))} />"
|
18
|
+
end
|
19
|
+
|
20
|
+
def content_tag(tag_name, value, attrs = {})
|
21
|
+
start_tag = "<#{tag_and_attributes(tag_name, tag_attributes(attrs))}>"
|
22
|
+
end_tag = "</#{tag_name}>"
|
23
|
+
[start_tag, value, end_tag].join
|
24
|
+
end
|
25
|
+
|
26
|
+
def tag_and_attributes(tag_name, attributes)
|
27
|
+
attributes.empty? ? tag_name : "#{tag_name} #{attributes}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def tag_attributes(hash)
|
31
|
+
hash.compact
|
32
|
+
.to_a
|
33
|
+
.map { |k, v| %(#{k}="#{v}") }.join(' ')
|
34
|
+
end
|
35
|
+
|
36
|
+
extend self
|
37
|
+
end
|
38
|
+
end
|
data/lib/auto_html.rb
CHANGED
@@ -1,17 +1,13 @@
|
|
1
|
-
|
2
|
-
require File.expand_path("../auto_html/#{f}", __FILE__)
|
3
|
-
end
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
|
3
|
+
# AutoHtml is a collection of filters that transform plain text into HTML code.
|
4
|
+
module AutoHtml
|
5
|
+
autoload :Pipeline, 'auto_html/pipeline'
|
6
|
+
autoload :TagHelper, 'auto_html/tag_helper'
|
7
|
+
autoload :Emoji, 'auto_html/emoji'
|
8
|
+
autoload :HtmlEscape, 'auto_html/html_escape'
|
9
|
+
autoload :Image, 'auto_html/image'
|
10
|
+
autoload :Link, 'auto_html/link'
|
11
|
+
autoload :Markdown, 'auto_html/markdown'
|
12
|
+
autoload :SimpleFormat, 'auto_html/simple_format'
|
7
13
|
end
|
8
|
-
|
9
|
-
# if rails
|
10
|
-
require 'auto_html/railtie' if defined?(Rails::Railtie)
|
11
|
-
if defined?(ActiveRecord::Base)
|
12
|
-
ActiveRecord::Base.send :include, AutoHtmlFor
|
13
|
-
|
14
|
-
module ActionView::Helpers::TextHelper
|
15
|
-
include AutoHtml
|
16
|
-
end
|
17
|
-
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe AutoHtml::Emoji do
|
6
|
+
it 'converts emoji to HTML' do
|
7
|
+
expect(subject.call(':joy:')).to eq(
|
8
|
+
'<img src="/images/emoji/unicode/1f602.png" class="emoji" title=":joy:" '\
|
9
|
+
'alt=":joy:" height="20" witdh="20" align="absmiddle" />'
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe AutoHtml::Image do
|
6
|
+
it 'transforms an image link to image tag' do
|
7
|
+
result = subject.call('http://rors.org/images/rails.png')
|
8
|
+
expect(result).to eq('<img src="http://rors.org/images/rails.png" />')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'transforms image link with a param to image tag' do
|
12
|
+
result = subject.call('http://farm4.static.flickr.com/3664/3512431377_71b8d002ef.jpg?v=0')
|
13
|
+
expect(result).to eq('<img src="http://farm4.static.flickr.com/3664/3512431377_71b8d002ef.jpg?v=0" />')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'transforms image link on https to image tag' do
|
17
|
+
result = subject.call('https://img.skitch.com/20100910-1wrbg5749xe29ya5t3s85bnaiy.png')
|
18
|
+
expect(result).to eq('<img src="https://img.skitch.com/20100910-1wrbg5749xe29ya5t3s85bnaiy.png" />')
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'transforms image link to a image tag with proxy as source' do
|
22
|
+
filter = AutoHtml::Image.new(proxy: 'https://proxy/?url=')
|
23
|
+
result = filter.call('http://img.skitch.com/20100910-1wrbg5749xe29ya5t3s85bnaiy.png')
|
24
|
+
expect(result).to eq('<img src="https://proxy/?url=http://img.skitch.com/20100910-1wrbg5749xe29ya5t3s85bnaiy.png" />')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'does not transforms already transformed image' do
|
28
|
+
result = subject.call('<img src="http://farm4.static.flickr.com/3459/3270173112_5099d3d730.jpg" />')
|
29
|
+
expect(result).to eq('<img src="http://farm4.static.flickr.com/3459/3270173112_5099d3d730.jpg" />')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'transforms an image link within text to image tag' do
|
33
|
+
result = subject.call('Which do you prefer, this one http://www.lockhartfineart.com/images/Rio_Grande_Frost.JPG, or this one http://rors.org/images/rails.png?')
|
34
|
+
expect(result).to eq('Which do you prefer, this one <img src="http://www.lockhartfineart.com/images/Rio_Grande_Frost.JPG" />, or this one <img src="http://rors.org/images/rails.png" />?')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'transforms an image link with a lot of param to image tag' do
|
38
|
+
result = subject.call('http://tbn3.google.com/images?q=tbn:vS-jtEi9Xc8K6M:http://upload.wikimedia.org/wikipedia/commons/b/ba/Potturinn.jpeg')
|
39
|
+
expect(result).to eq('<img src="http://tbn3.google.com/images?q=tbn:vS-jtEi9Xc8K6M:http://upload.wikimedia.org/wikipedia/commons/b/ba/Potturinn.jpeg" />')
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe AutoHtml::Link do
|
6
|
+
it 'transforms URL to a link' do
|
7
|
+
result = subject.call('<a href="https://www.ruby-lang.org/en/" >https://www.ruby-lang.org/en/</a>')
|
8
|
+
expect(result).to eq '<a href="https://www.ruby-lang.org/en/" >https://www.ruby-lang.org/en/</a>'
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'transforms URL with param to a link' do
|
12
|
+
result = subject.call('http://example.com/abc?query=ruby')
|
13
|
+
expect(result).to eq '<a href="http://example.com/abc?query=ruby">http://example.com/abc?query=ruby</a>'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'transforms URL with param and a trailing dot' do
|
17
|
+
result = subject.call('http://example.com/abc?query=ruby.')
|
18
|
+
expect(result).to eq '<a href="http://example.com/abc?query=ruby">http://example.com/abc?query=ruby</a>.'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'transforms URL with anchor and a trailing dot' do
|
22
|
+
result = subject.call('http://example.com/example#id=123.12.')
|
23
|
+
expect(result).to eq '<a href="http://example.com/example#id=123.12">http://example.com/example#id=123.12</a>.'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'transforms URL with commas' do
|
27
|
+
result = subject.call('http://www.dw-world.de/dw/article/0,,4708386,00.html?maca=ser-rss-ser-all-1494-rdf')
|
28
|
+
expect(result).to eq '<a href="http://www.dw-world.de/dw/article/0,,4708386,00.html?maca=ser-rss-ser-all-1494-rdf">http://www.dw-world.de/dw/article/0,,4708386,00.html?maca=ser-rss-ser-all-1494-rdf</a>'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'transforms complex URL' do
|
32
|
+
result = subject.call('http://www.google.com/#q=nikola+tesla&ct=tesla09&oi=ddle&fp=Xmf0jJ9P_V0')
|
33
|
+
expect(result).to eq '<a href="http://www.google.com/#q=nikola+tesla&ct=tesla09&oi=ddle&fp=Xmf0jJ9P_V0">http://www.google.com/#q=nikola+tesla&ct=tesla09&oi=ddle&fp=Xmf0jJ9P_V0</a>'
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'transforms with target options' do
|
37
|
+
filter = described_class.new(target: '_blank')
|
38
|
+
result = filter.call('http://rors.org')
|
39
|
+
expect(result).to eq '<a href="http://rors.org" target="_blank">http://rors.org</a>'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'transforms with rel options' do
|
43
|
+
filter = described_class.new(rel: 'nofollow')
|
44
|
+
result = filter.call('http://rors.org')
|
45
|
+
expect(result).to eq '<a href="http://rors.org" rel="nofollow">http://rors.org</a>'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'transforms with target and rel options' do
|
49
|
+
filter = described_class.new(target: '_blank', rel: 'nofollow')
|
50
|
+
result = filter.call('http://rors.org')
|
51
|
+
expect(result).to eq '<a href="http://rors.org" target="_blank" rel="nofollow">http://rors.org</a>'
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe AutoHtml::Pipeline do
|
6
|
+
subject { described_class.new(AutoHtml::SimpleFormat.new, AutoHtml::Image.new, AutoHtml::Link.new) }
|
7
|
+
|
8
|
+
it 'does not transforms input when no filters provided' do
|
9
|
+
input = 'Hey check out my blog => http://rors.org'
|
10
|
+
result = described_class.new.call(input)
|
11
|
+
expect(result).to eq input
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'transforms input using provided filters' do
|
15
|
+
result = subject.call 'Check the logo: http://rors.org/images/rails.png. Visit: http://rubyonrails.org'
|
16
|
+
expect(result).to eq '<p>Check the logo: <img src="http://rors.org/images/rails.png" />. Visit: <a href="http://rubyonrails.org">http://rubyonrails.org</a></p>'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'is blank if input is blank' do
|
20
|
+
expect(subject.call('')).to eq ''
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'is blank if input is nil' do
|
24
|
+
expect(subject.call(nil)).to eq ''
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe AutoHtml::SimpleFormat do
|
6
|
+
it 'formats input using simple rules' do
|
7
|
+
result = subject.call('Hey check out my blog => http://rors.org')
|
8
|
+
expect(result).to eq '<p>Hey check out my blog => http://rors.org</p>'
|
9
|
+
|
10
|
+
expect(subject.call("crazy\r\n cross\r platform linebreaks")).to eq "<p>crazy\n<br /> cross\n<br /> platform linebreaks</p>"
|
11
|
+
expect(subject.call("A paragraph\n\nand another one!")).to eq "<p>A paragraph</p>\n\n<p>and another one!</p>"
|
12
|
+
expect(subject.call("A paragraph\n With a newline")).to eq "<p>A paragraph\n<br /> With a newline</p>"
|
13
|
+
|
14
|
+
expect(subject.call("A\nB\nC\nD")).to eq "<p>A\n<br />B\n<br />C\n<br />D</p>"
|
15
|
+
|
16
|
+
expect(subject.call("A\r\n \nB\n\n\r\n\t\nC\nD")).to eq "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>"
|
17
|
+
end
|
18
|
+
end
|