- wysiwyg source code: https://github.com/Voog/wysihtml
- Example usage: https://github.com/LaunchPadLab/fuel (note that this is a Rails Engine, so the implementation is more complex and just plain different than it would be in a regular Rails app).
- Add the following javascript files to app/assets/javascripts/:
- Primary JS file: wysithml-toolbar.min.js
- Parser Rules: advanced_and_extended.js
- In application.js:
//= require fuel/wysihtml5x-toolbar.min.js
-
Add the following CSS file to app/assets/stylesheets/: wysihtml.css
-
Add a file app/assets/stylesheets/components/_editor.scss:
// Editor Component
// ========================================
// Variables
$editor-background-color: $white-dark;
$editor-border: 1px solid $grey-base;
$editor-border-radius: 3px;
$editor-margin: 0 0 -2px;
$editor-button-color: $black-base;
$editor-button-padding: 5px 12px;
$editor-button-transition: all 0.2s ease-in-out;
// Structure
#editor {
@include clearfix;
@include position(relative);
@include rem(margin, $editor-margin);
background-color: $editor-background-color;
border: $editor-border;
border-top-left-radius: $editor-border-radius;
border-top-right-radius: $editor-border-radius;
padding: 0;
}
.editor-button{
@include rem(padding, $editor-button-padding);
@include transition($editor-button-transition);
color: $editor-button-color;
display: inline-block;
margin: 0;
&:nth-child(even) {
border-left: $editor-border;
border-right: $editor-border;
}
&:hover,
&.wysihtml5-command-dialog-opened {
cursor: pointer;
color: $blue-base;
}
}
- Make sure to @import this file in application.scss (note that the wysihtml.css should not be required in application.scss):
@import 'components/editor';
- Add the HTML:
<fieldset>
<%= f.label :content, 'Your Post' %>
<ul id="editor" style="display: none;">
<li class="editor-button" data-wysihtml5-command="bold" title="CTRL+B"><i class="fa fa-bold"></i></li>
<li class="editor-button" data-wysihtml5-command="italic" title="CTRL+I"><i class="fa fa-italic"></i></li>
<li class="editor-button" data-wysihtml5-command="insertUnorderedList"><i class="fa fa-list-ul"></i></li>
<li class="editor-button" data-wysihtml5-command="insertOrderedList"><i class="fa fa-list-ol"></i></li>
<li class="editor-button" data-wysihtml5-command="createLink"><i class="fa fa-link"></i></li>
<li class="editor-button" data-wysihtml5-command="insertImage"><i class="fa fa-image"></i></li>
<li class="editor-button" data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h2"><i class="fa fa-header"></i></li>
<li class="editor-button" data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="p"><i class="fa fa-paragraph"></i></li>
<li class="editor-button" data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="blockquote"><i class="fa fa-quote-left"></i></li>
<li class="editor-button" data-wysihtml5-command="formatCode" data-wysihtml5-command-value="language-html"><i class="fa fa-code"></i></li>
<li class="editor-button" data-wysihtml5-command="undo"><i class="fa fa-undo"></i></li>
<li class="editor-button" data-wysihtml5-action="change_view">HTML</li>
<div data-wysihtml5-dialog="createLink" style="display: none;" class="card card__fixed">
<fieldset class="small">
<label>Link URL:</label>
<input data-wysihtml5-dialog-field="href" value="http://">
</fieldset>
<fieldset class="margin--clear right">
<a data-wysihtml5-dialog-action="save" class="save-link button button--primary button--small margin--clear">Insert</a>
</fieldset>
</div>
<div data-wysihtml5-dialog="insertImage" style="display: none;" class="card card__fixed">
<fieldset class="small">
<div class="half-group">
<label> Upload Image: <%= file_field_tag :image, id: "image-upload", class: "directUpload" %> </label>
</div>
<div class="half-group">
<label> Or Image URL: <input data-wysihtml5-dialog-field="src" value="http://" id="imageUrl"> </label>
</div>
</fieldset>
<fieldset class="small">
<label>
Align:
<select data-wysihtml5-dialog-field="className">
<option value="">default</option>
<option value="wysiwyg-float-left">left</option>
<option value="wysiwyg-float-right">right</option>
</select>
</label>
</fieldset>
<fieldset class="right margin--clear">
<a data-wysihtml5-dialog-action="save" class="save-link button button--primary button--small margin--clear">Insert</a>
<a data-wysihtml5-dialog-action="cancel" class="button button--danger button--small margin--clear">Cancel</a>
</fieldset>
</div>
</ul>
<%= f.text_area :content, class: 'post' %>
</fieldset>
Gemfile:
gem "aws-sdk", '< 2.0'
Terminal:
bundle
Set ENV variables in application.yml (using something like Figaro):
AWS_ACCESS_KEY: ksjfsldkjflsdkfj
AWS_SECRET_ACCESS_KEY: sldjfkslkjdf
development:
AWS_BUCKET: my-dev-bucket
production
AWS_BUCKET: my-prod-bucket
config/initializers/aws.rb:
AWS.config( access_key_id: ENV["AWS_ACCESS_KEY"],
secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"] )
Create a partial _editor.html.erb and replace editorId with the ID of your textarea field from the editor HTML above:
<script>
$(function() {
$body = $("body");
var editorId = "id_of_textarea_field";
var editorStylesheetPath = "<%= stylesheet_path('wysihtml') %>";
var editorSubmitUrl = '<%= s3_direct_post.url %>';
var editorFormData = JSON.parse('<%= s3_direct_post.fields.to_json.html_safe %>');
var editorHostUrl = '//<%= @s3_direct_post.url.host %>/';
var editor = new wysihtml5.Editor(editorId, { // id of textarea element
toolbar: "editor", // id of toolbar element
parserRules: wysihtml5ParserRules,
stylesheets: editorStylesheetPath
});
<% if s3_direct_post %>
$('.directUpload').each(function(i, elem) {
var fileInput = $(elem);
var submitButton = $(".save-button");
var progressBar = $("<div class='bar'></div>");
var barContainer = $("<div class='progress'></div>").append(progressBar);
fileInput.after(barContainer);
fileInput.fileupload({
fileInput: fileInput,
url: editorSubmitUrl,
type: 'POST',
autoUpload: true,
formData: editorFormData,
paramName: 'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
dataType: 'XML', // S3 returns XML if success_action_status is set to 201
replaceFileInput: false,
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css('width', progress + '%')
},
start: function (e) {
submitButton.prop('disabled', true);
progressBar.
css('background', '#C4DC6E').
css('display', 'block').
text("Loading...");
},
done: function(e, data) {
submitButton.prop('disabled', false);
progressBar.text("Uploading done");
// extract key and generate URL from response
var key = $(data.jqXHR.responseXML).find("Key").text();
var newUrl = editorHostUrl + key;
console.log(newUrl);
// set url in form
$("#imageUrl").val(newUrl);
},
fail: function(e, data) {
submitButton.prop('disabled', false);
progressBar.
css("background", "#F7917C").
text("Failed");
}
});
});
<% end %>
});
</script>
Make sure to render the partial:
<%= render 'editor' %>
- In application_helper.rb:
def s3_bucket
bucket = ENV["AWS_BUCKET"]
return nil unless bucket.present?
@s3_bucket ||= (
AWS::S3.new.buckets[bucket]
)
end
def s3_direct_post
@s3_direct_post ||= (
return unless s3_bucket.present?
s3_bucket.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: 201, acl: :public_read)
)
end