bootsy 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +38 -14
- data/app/assets/images/bootsy/gallery-loader.gif +0 -0
- data/app/assets/images/bootsy/{ajax-loader.gif → upload-loader.gif} +0 -0
- data/app/assets/javascripts/bootsy/bootstrap-wysihtml5.js +59 -51
- data/app/assets/javascripts/bootsy/bootstrap.file-input.js +1 -1
- data/app/assets/javascripts/bootsy/bootsy.js +242 -185
- data/app/assets/javascripts/bootsy/editor_options.js +4 -4
- data/app/assets/javascripts/bootsy/init.js +4 -1
- data/app/assets/javascripts/bootsy/locales/bootsy.pt-BR.js +3 -2
- data/app/assets/javascripts/bootsy/translations.js +1 -1
- data/app/assets/stylesheets/bootsy/bootstrap-submenu.css +47 -0
- data/app/assets/stylesheets/bootsy/bootsy.css +20 -31
- data/app/controllers/bootsy/images_controller.rb +23 -9
- data/app/helpers/bootsy/application_helper.rb +1 -1
- data/app/views/bootsy/images/_image.html.erb +43 -0
- data/app/views/bootsy/images/_modal.html.erb +25 -10
- data/app/views/bootsy/images/_new.html.erb +4 -12
- data/lib/bootsy.rb +9 -1
- data/lib/bootsy/form_helper.rb +10 -10
- data/lib/bootsy/version.rb +1 -1
- metadata +13 -14
- data/app/views/bootsy/images/_index.html.erb +0 -48
- data/app/views/bootsy/images/create.js.erb +0 -2
- data/app/views/bootsy/images/destroy.js.erb +0 -1
- data/config/bootsy.yml +0 -0
@@ -1,11 +1,11 @@
|
|
1
1
|
window.Bootsy = window.Bootsy || {};
|
2
2
|
|
3
|
-
var
|
3
|
+
var pageStylesheets = [];
|
4
4
|
$('link[rel="stylesheet"]').each(function () {
|
5
|
-
|
5
|
+
pageStylesheets.push($(this).attr('href'));
|
6
6
|
});
|
7
7
|
|
8
|
-
window.Bootsy.
|
8
|
+
window.Bootsy.options = {
|
9
9
|
parserRules: {
|
10
10
|
classes: {
|
11
11
|
"wysiwyg-color-silver" : 1,
|
@@ -76,5 +76,5 @@ window.Bootsy.editorOptions = {
|
|
76
76
|
}
|
77
77
|
},
|
78
78
|
color: true,
|
79
|
-
stylesheets:
|
79
|
+
stylesheets: pageStylesheets
|
80
80
|
};
|
@@ -0,0 +1,47 @@
|
|
1
|
+
.dropdown-submenu {
|
2
|
+
position:relative;
|
3
|
+
}
|
4
|
+
|
5
|
+
.dropdown-submenu>.dropdown-menu {
|
6
|
+
top: 0;
|
7
|
+
left: 100%;
|
8
|
+
margin-top: -6px;
|
9
|
+
margin-left: -1px;
|
10
|
+
-webkit-border-radius: 0 6px 6px 6px;
|
11
|
+
-moz-border-radius: 0 6px 6px 6px;
|
12
|
+
border-radius: 0 6px 6px 6px;
|
13
|
+
}
|
14
|
+
|
15
|
+
.dropdown-submenu:hover > .dropdown-menu {
|
16
|
+
display:block;
|
17
|
+
}
|
18
|
+
|
19
|
+
.dropdown-submenu > a:after {
|
20
|
+
display: block;
|
21
|
+
content: " ";
|
22
|
+
float: right;
|
23
|
+
width: 0;
|
24
|
+
height: 0;
|
25
|
+
border-color: transparent;
|
26
|
+
border-style: solid;
|
27
|
+
border-width: 5px 0 5px 5px;
|
28
|
+
border-left-color: #cccccc;
|
29
|
+
margin-top: 5px;
|
30
|
+
margin-right: -10px;
|
31
|
+
}
|
32
|
+
|
33
|
+
.dropdown-submenu:hover > a:after {
|
34
|
+
border-left-color: #ffffff;
|
35
|
+
}
|
36
|
+
|
37
|
+
.dropdown-submenu.pull-left {
|
38
|
+
float:none;
|
39
|
+
}
|
40
|
+
|
41
|
+
.dropdown-submenu.pull-left > .dropdown-menu {
|
42
|
+
left: -100%;
|
43
|
+
margin-left: 10px;
|
44
|
+
-webkit-border-radius: 6px 0 6px 6px;
|
45
|
+
-moz-border-radius: 6px 0 6px 6px;
|
46
|
+
border-radius: 6px 0 6px 6px;
|
47
|
+
}
|
@@ -72,10 +72,6 @@
|
|
72
72
|
margin: 0 0 8px 8px;
|
73
73
|
}
|
74
74
|
|
75
|
-
form.bootsy .field_with_errors {
|
76
|
-
display: inline-block;
|
77
|
-
}
|
78
|
-
|
79
75
|
blockquote {
|
80
76
|
padding: 0 0 0 15px;
|
81
77
|
margin: 0 0 20px;
|
@@ -84,9 +80,6 @@ blockquote {
|
|
84
80
|
|
85
81
|
blockquote p {
|
86
82
|
margin-bottom: 0;
|
87
|
-
/*font-size: 16px;
|
88
|
-
font-weight: 300;
|
89
|
-
line-height: 25px;*/
|
90
83
|
}
|
91
84
|
|
92
85
|
blockquote small {
|
@@ -131,38 +124,27 @@ textarea.bootsy:required:invalid {
|
|
131
124
|
color: inherit;
|
132
125
|
}
|
133
126
|
|
134
|
-
/*Allow dropdown menus to show be displayed if they overflow the footer or modal*/
|
135
|
-
#bootsy_image_gallery .modal { overflow: visible; }
|
136
|
-
#bootsy_image_gallery .modal-body { overflow-y: visible; }
|
137
|
-
|
138
127
|
/*Modal Design Styles*/
|
139
|
-
|
140
|
-
|
128
|
+
|
129
|
+
.bootsy-modal .file-input-name {display: none;} /*Hide the input file name from showing as it's not needed and ruins design*/
|
130
|
+
|
141
131
|
/*Set a min-height on the modal body to prevent jumping up and down of modal when adding content*/
|
142
|
-
|
143
|
-
min-height:
|
144
|
-
margin-bottom: 0;
|
145
|
-
padding-bottom: 7px;
|
146
|
-
}
|
147
|
-
#bootsy_image_gallery .modal-body .thumbnails {
|
148
|
-
margin-left: -10px;
|
149
|
-
margin-bottom: 0;
|
150
|
-
}
|
151
|
-
#bootsy_image_gallery .modal-body .thumbnails > li {
|
152
|
-
margin-left: 7px;
|
153
|
-
margin-bottom: 8px;
|
132
|
+
.bootsy-modal .modal-body {
|
133
|
+
min-height: 120px;
|
154
134
|
}
|
155
135
|
|
156
|
-
|
157
|
-
|
158
|
-
margin-right: 8px;
|
136
|
+
.bootsy-image {
|
137
|
+
margin-bottom: 15px;
|
159
138
|
}
|
160
|
-
|
139
|
+
|
140
|
+
.bootsy-upload-loader {
|
161
141
|
display: inline-block;
|
162
142
|
padding-right: 5px;
|
163
143
|
}
|
164
|
-
|
165
|
-
|
144
|
+
|
145
|
+
.bootsy-gallery-loader {
|
146
|
+
display: block;
|
147
|
+
margin: 0 auto;
|
166
148
|
}
|
167
149
|
|
168
150
|
/*The below code is needed for capybara to be able to find the input, as it*/
|
@@ -170,3 +152,10 @@ textarea.bootsy:required:invalid {
|
|
170
152
|
.file-input-wrapper input[type=file], .file-input-wrapper input[type=file]:focus, .file-input-wrapper input[type=file]:hover {
|
171
153
|
opacity: .01;
|
172
154
|
}
|
155
|
+
|
156
|
+
/* Avoid Boostrap input borders */
|
157
|
+
body.bootsy_text_area.form-control {
|
158
|
+
border: none;
|
159
|
+
-webkit-box-shadow: none;
|
160
|
+
box-shadow: none;
|
161
|
+
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require_dependency
|
1
|
+
require_dependency 'bootsy/application_controller'
|
2
2
|
|
3
3
|
module Bootsy
|
4
4
|
class ImagesController < Bootsy::ApplicationController
|
@@ -11,10 +11,19 @@ module Bootsy
|
|
11
11
|
respond_to do |format|
|
12
12
|
format.html # index.html.erb
|
13
13
|
format.json do
|
14
|
-
|
14
|
+
rendered_images = []
|
15
|
+
|
16
|
+
@images.each do |image|
|
17
|
+
rendered_images << render_to_string(file: 'bootsy/images/_image',
|
15
18
|
formats: [:html],
|
16
|
-
locals: {
|
17
|
-
|
19
|
+
locals: { image: image })
|
20
|
+
end
|
21
|
+
|
22
|
+
new_image = render_to_string(file: 'bootsy/images/_new',
|
23
|
+
formats: [:html],
|
24
|
+
locals: { gallery: @gallery, image: @gallery.images.new })
|
25
|
+
|
26
|
+
render json: { images: rendered_images, form: new_image }
|
18
27
|
end
|
19
28
|
end
|
20
29
|
end
|
@@ -29,10 +38,16 @@ module Bootsy
|
|
29
38
|
|
30
39
|
respond_to do |format|
|
31
40
|
if @image.save
|
32
|
-
|
33
|
-
|
41
|
+
image_view = render_to_string(file: 'bootsy/images/_image',
|
42
|
+
formats: [:html],
|
43
|
+
locals: { image: @image })
|
44
|
+
|
45
|
+
new_image = render_to_string(file: 'bootsy/images/_new',
|
46
|
+
formats: [:html],
|
47
|
+
locals: { gallery: @gallery, image: @gallery.images.new })
|
48
|
+
|
49
|
+
format.json { render json: { image: image_view, form: new_image, gallery_id: @gallery.id } }
|
34
50
|
else
|
35
|
-
format.js
|
36
51
|
format.json { render json: @image.errors, status: :unprocessable_entity }
|
37
52
|
end
|
38
53
|
end
|
@@ -45,9 +60,8 @@ module Bootsy
|
|
45
60
|
@image.destroy
|
46
61
|
|
47
62
|
respond_to do |format|
|
48
|
-
format.
|
63
|
+
format.json { render json: { id: params[:id] } }
|
49
64
|
format.html { redirect_to images_url }
|
50
|
-
format.json { head :no_content }
|
51
65
|
end
|
52
66
|
end
|
53
67
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Bootsy
|
2
2
|
module ApplicationHelper
|
3
3
|
def refresh_btn
|
4
|
-
link_to t('bootsy.action.refresh'), '#refresh-gallery', class: 'btn btn-
|
4
|
+
link_to t('bootsy.action.refresh'), '#refresh-gallery', class: 'btn btn-default btn-sm refresh-btn'
|
5
5
|
end
|
6
6
|
|
7
7
|
def resource_or_nil resource
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<div class="bootsy-image dropdown col-md-2" data-id="<%= image.id %>">
|
2
|
+
<%= link_to image_tag(image.image_file.thumb.url), '#', class: 'thumbnail',
|
3
|
+
data: { toggle: 'dropdown' } %>
|
4
|
+
|
5
|
+
<ul class="dropdown-menu" role="menu">
|
6
|
+
<li role="presentation" class="dropdown-header"><%= t 'bootsy.image.size' %></li>
|
7
|
+
|
8
|
+
<% Bootsy.image_versions_available.each do |size| %>
|
9
|
+
<li role="presentation" class="dropdown dropdown-submenu">
|
10
|
+
<%= link_to '#', tabindex: '-1', role: 'menuitem', data: { toggle: 'dropdown' } do %>
|
11
|
+
<i class="icon-picture"></i>
|
12
|
+
|
13
|
+
<%= t size, scope: 'bootsy.image' %>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<ul class="dropdown-menu" role="menu">
|
17
|
+
<% [:left, :right, :inline].each do |position| %>
|
18
|
+
<li role="presentation">
|
19
|
+
<%= link_to '#', class: 'insert', tabindex: '-1', role: 'menuitem',
|
20
|
+
data: { image_size: size.to_s, position: position.to_s} do %>
|
21
|
+
<%= t position, scope: 'bootsy.image.position' %>
|
22
|
+
<% end %>
|
23
|
+
</li>
|
24
|
+
<% end %>
|
25
|
+
</ul>
|
26
|
+
</li>
|
27
|
+
<% end %>
|
28
|
+
|
29
|
+
<% if Bootsy.allow_destroy %>
|
30
|
+
<li role="presentation" class="divider"></li>
|
31
|
+
|
32
|
+
<li role="presentation">
|
33
|
+
<%= link_to image, method: :delete, remote: true,
|
34
|
+
data: { confirm: t('bootsy.image.confirm.destroy'), type: 'json' },
|
35
|
+
tabindex: '-1', role: 'menuitem', class: 'destroy-btn' do %>
|
36
|
+
<i class="icon-trash"></i>
|
37
|
+
|
38
|
+
<%= t('bootsy.action.destroy') %>
|
39
|
+
<% end %>
|
40
|
+
</li>
|
41
|
+
<% end %>
|
42
|
+
</ul>
|
43
|
+
</div>
|
@@ -1,13 +1,28 @@
|
|
1
|
-
<div class="modal
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
1
|
+
<div class="modal fade bootsy-modal" tabindex="-1"
|
2
|
+
role="dialog" aria-hidden="true" data-no-turbolink
|
3
|
+
data-gallery-id="<%= container.bootsy_image_gallery_id %>">
|
4
|
+
<div class="modal-dialog">
|
5
|
+
<div class="modal-content">
|
6
|
+
<div class="modal-header">
|
7
|
+
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
8
|
+
|
9
|
+
<h3 class="modal-title"><%= t 'bootsy.image.p' %></h3>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<div class="modal-body">
|
13
|
+
<div class="alert bootsy-empty-alert">
|
14
|
+
<%= t 'bootsy.no_images_uploaded' %>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div class="row bootsy-gallery"></div>
|
18
|
+
|
19
|
+
<%= image_tag 'bootsy/gallery-loader.gif', class: 'bootsy-gallery-loader' %>
|
20
|
+
|
21
|
+
<%= refresh_btn %>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="modal-footer">
|
25
|
+
</div>
|
11
26
|
</div>
|
12
27
|
</div>
|
13
28
|
</div>
|
@@ -1,16 +1,8 @@
|
|
1
|
-
<%= form_for
|
1
|
+
<%= form_for([bootsy, resource_or_nil(gallery), image], remote: true,
|
2
|
+
html: { multipart: true, class: 'bootsy-upload-form form-inline' }, data: { type: 'json' }) do |f| %>
|
2
3
|
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
|
3
|
-
<% if image.errors.any? %>
|
4
|
-
<% image.errors.full_messages.each do |msg| %>
|
5
|
-
<div class="alert alert-error"><%= msg %></div>
|
6
|
-
<% end %>
|
7
|
-
<% end %>
|
8
4
|
|
9
|
-
|
10
|
-
<%= image_tag "bootsy/ajax-loader.gif" %>
|
11
|
-
</div>
|
12
|
-
|
13
|
-
<%= f.file_field :image_file, title: t('bootsy.action.upload') %>
|
5
|
+
<%= image_tag 'bootsy/upload-loader.gif', class: 'bootsy-upload-loader' %>
|
14
6
|
|
15
|
-
<%= f.
|
7
|
+
<%= f.file_field :image_file, title: t('bootsy.action.upload') %>
|
16
8
|
<% end %>
|
data/lib/bootsy.rb
CHANGED
@@ -13,7 +13,15 @@ module Bootsy
|
|
13
13
|
|
14
14
|
# Default editor options
|
15
15
|
mattr_accessor :editor_options
|
16
|
-
@@editor_options = {
|
16
|
+
@@editor_options = {
|
17
|
+
font_styles: true,
|
18
|
+
emphasis: true,
|
19
|
+
lists: true,
|
20
|
+
html: false,
|
21
|
+
link: true,
|
22
|
+
image: true,
|
23
|
+
color: true
|
24
|
+
}
|
17
25
|
|
18
26
|
# Image versions available
|
19
27
|
mattr_accessor :image_versions_available
|
data/lib/bootsy/form_helper.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
module Bootsy
|
2
2
|
module FormHelper
|
3
3
|
def bootsy_area(object, method, options = {})
|
4
|
-
container = options.delete
|
5
|
-
enable_uploader = enable_uploader? object, options.delete(:uploader), container
|
4
|
+
container = options.delete(:container)
|
6
5
|
bootsy_options = Bootsy.editor_options.merge(options.delete(:editor_options) || {})
|
7
|
-
bootsy_options[:uploader] = false unless enable_uploader
|
8
6
|
|
9
|
-
|
10
|
-
options[:class] = class_attr options
|
7
|
+
bootsy_options[:uploader] = enable_uploader?(object, options.delete(:uploader), container)
|
11
8
|
|
12
|
-
|
9
|
+
options[:data] = data_options(options, bootsy_options)
|
10
|
+
options[:class] = class_attr(options)
|
13
11
|
|
14
|
-
|
15
|
-
|
12
|
+
output = self.text_area(object_name(object), method, options)
|
13
|
+
|
14
|
+
if bootsy_options[:uploader]
|
15
|
+
output += self.render 'bootsy/images/modal', { container: container || object }
|
16
16
|
|
17
17
|
if container.blank? || (container == object)
|
18
|
-
output += self.hidden_field
|
18
|
+
output += self.hidden_field(object_name(object), :bootsy_image_gallery_id, class: 'bootsy_image_gallery_id')
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -57,7 +57,7 @@ module Bootsy
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def data_options(options, bootsy_options)
|
60
|
-
(options[:data] || {}).merge Hash[bootsy_options.map{|k,v|["bootsy-#{k}",v]}]
|
60
|
+
(options[:data] || {}).merge Hash[ bootsy_options.map { |k,v| ["bootsy-#{k}", v] } ]
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
data/lib/bootsy/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bootsy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Volmer Soares
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mini_magick
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - ~>
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 1.
|
89
|
+
version: '1.2'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - ~>
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 1.
|
96
|
+
version: '1.2'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: cucumber-rails
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,28 +114,28 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - ~>
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '2.
|
117
|
+
version: '2.4'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - ~>
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '2.
|
124
|
+
version: '2.4'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: selenium-webdriver
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - ~>
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '2.
|
131
|
+
version: '2.37'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - ~>
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '2.
|
138
|
+
version: '2.37'
|
139
139
|
description: A beautiful WYSIWYG editor with image uploads for Rails.
|
140
140
|
email:
|
141
141
|
- volmerius@gmail.com
|
@@ -143,7 +143,8 @@ executables: []
|
|
143
143
|
extensions: []
|
144
144
|
extra_rdoc_files: []
|
145
145
|
files:
|
146
|
-
- app/assets/images/bootsy/
|
146
|
+
- app/assets/images/bootsy/gallery-loader.gif
|
147
|
+
- app/assets/images/bootsy/upload-loader.gif
|
147
148
|
- app/assets/javascripts/bootsy/bootstrap-wysihtml5.js
|
148
149
|
- app/assets/javascripts/bootsy/bootstrap.file-input.js
|
149
150
|
- app/assets/javascripts/bootsy/bootsy.js
|
@@ -154,6 +155,7 @@ files:
|
|
154
155
|
- app/assets/javascripts/bootsy/translations.js
|
155
156
|
- app/assets/javascripts/bootsy/wysihtml5.js
|
156
157
|
- app/assets/javascripts/bootsy.js
|
158
|
+
- app/assets/stylesheets/bootsy/bootstrap-submenu.css
|
157
159
|
- app/assets/stylesheets/bootsy/bootstrap-wysihtml5.css
|
158
160
|
- app/assets/stylesheets/bootsy/bootsy.css
|
159
161
|
- app/assets/stylesheets/bootsy.css
|
@@ -161,12 +163,9 @@ files:
|
|
161
163
|
- app/controllers/bootsy/images_controller.rb
|
162
164
|
- app/helpers/bootsy/application_helper.rb
|
163
165
|
- app/uploaders/bootsy/image_uploader.rb
|
164
|
-
- app/views/bootsy/images/
|
166
|
+
- app/views/bootsy/images/_image.html.erb
|
165
167
|
- app/views/bootsy/images/_modal.html.erb
|
166
168
|
- app/views/bootsy/images/_new.html.erb
|
167
|
-
- app/views/bootsy/images/create.js.erb
|
168
|
-
- app/views/bootsy/images/destroy.js.erb
|
169
|
-
- config/bootsy.yml
|
170
169
|
- config/cucumber.yml
|
171
170
|
- config/locales/en.yml
|
172
171
|
- config/locales/pt-BR.yml
|
@@ -210,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
209
|
version: '0'
|
211
210
|
requirements: []
|
212
211
|
rubyforge_project:
|
213
|
-
rubygems_version: 2.
|
212
|
+
rubygems_version: 2.1.3
|
214
213
|
signing_key:
|
215
214
|
specification_version: 4
|
216
215
|
summary: A beautiful WYSIWYG editor with image uploads for Rails.
|