Skip to content

Instantly share code, notes, and snippets.

@francirp
Last active August 29, 2015 14:25
Show Gist options
  • Save francirp/9958f4578a47c803fc71 to your computer and use it in GitHub Desktop.
Save francirp/9958f4578a47c803fc71 to your computer and use it in GitHub Desktop.
Using wysihtml in Rails

How I installed wysithml rich text editor into a Rails app with direct to S3 image uploading

Overview

Implementation

//= 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>

Upload Images Direct to S3

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  
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment