LoginSignup
12
7

More than 5 years have passed since last update.

Railsにmedium-editorでMedium.comライクな投稿機能を導入する

Last updated at Posted at 2018-05-05

はじめに

Ruby on RailsでMediumEditorを導入するまで。
Medium.comのかっこいいエディタをRailsプロジェクトに簡単に導入出来るので、オススメのWysiwygエディタです。

環境

  • Ruby 2.4.0
  • Rails 5.2.0

1. Railsプロジェクトの作成

Railsプロジェクトの作成からやっていきます。

$ rails new medium-editor

以上でmedium-editorというプロジェクトを作成します。

2. gem install

使用するgemをインストールします。
Gemfileを開き以下を入力します。

# medium editor
gem 'medium-editor-rails'
gem 'medium-editor-insert-plugin-rails'

以上を記載したら、bundle installを行います。
gemをインストールしたら、medium-editorで使用するjsとcssを読み込ませます。

app/assets/javascripts/application.jsに以下を追記

//= require medium-editor
//= require medium-editor-insert-plugin

app/assets/stylesheets/application.cssに以下を追記

*= require medium-editor/medium-editor
*= require medium-editor/themes/beagle
*= require medium-editor-insert-plugin

3. scaffoldで投稿機能の作成

scaffoldでCRUDの投稿機能をささっと作ってしまいます。

$ rails g scaffold Article title:string body:text

$ rails db:migrate

ここまで実行したら、http://localhost:3000/articlesを確認して見ましょう。

4. routes.rbを変更する

http://localhost:3000/articlesでもいいのですが、articlesと入力しなくてもアクセスが出来るようにしておきます。
config/routes.rbに以下のようにします。

Rails.application.routes.draw do
  resources :articles

  root 'articles#index'
end

5. フォームにmedium-editorを適用させる

app/views/articles/_form.html.erbを開く

テキストエリアにeditableというクラスを付与させておく。
該当ファイルの最終行に以下を追記することで、medium-editorを適用することが出来る。

<script>
  var editor = new MediumEditor('.editable', {
    // placeholder settings
    placeholder: {
      text: 'Type your story',
      hideOnClick: true
    }
  });

  $('.editable').mediumInsert({
    editor: editor
  });
</script>

6. 見た目を整える

scaffoldをしただけでは、寂しいので、Siimpleを使って見た目を最低限整えてあげましょう。

layouts/application.html.erb

app/views/layouts/application.rbを以下のようにします。
今回はCDN経由でSiimpleを使えるようにしておきます。

<!DOCTYPE html>
<html>
  <head>
    <title>MediumEditor</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/siimple@3.0.0/dist/siimple.min.css">
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

articles/index.html.erb

app/views/articles/index.html.erb

<div class="siimple-content">
  <p id="notice"><%= notice %></p>

  <h1 class="siimple-h2">Articles</h1>

  <table class="siimple-box">
    <thead>
      <tr>
        <th>Title</th>
        <th>Body</th>
        <th colspan="3"></th>
      </tr>
    </thead>

    <tbody>
      <% @articles.each do |article| %>
        <tr>
          <td><%= article.title %></td>
          <td><%= link_to 'Show', article, class: 'siimple-link' %></td>
          <td><%= link_to 'Edit', edit_article_path(article), class: "siimple-link" %></td>
          <td><%= link_to 'Destroy', article, class: 'siimple-link', method: :delete, data: { confirm: 'Are you sure?' } %></td>
        </tr>
      <% end %>
    </tbody>
  </table>

  <br>

  <%= link_to 'New Article', new_article_path, class: 'siimple-link' %>
</div>

articles/show.html.erb

app/views/articles/show.html.erb

<div class="siimple-size siimple-size--large">
  <div class="siimple-box">
    <p id="notice"><%= notice %></p>

    <p class="siimple-box-title">
      <strong>Title:</strong>
      <%= @article.title %>
    </p>

    <p class="siimple-box-detail">
      <strong>Body:</strong>
      <%= @article.body.html_safe %>
    </p>
  </div>
  <%= link_to 'Edit', edit_article_path(@article), class: 'siimple-link' %> |
  <%= link_to 'Back', articles_path, class: 'siimple-link' %>
</div>

articles/_form.html.erb

app/views/articles/_form.html.erb

<%= form_with(model: article, local: true, class: 'siimple-form') do |form| %>
  <% if article.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>

      <ul>
      <% article.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="siimple-form-field">
    <%= form.label :title, class: 'siimple-label' %>
    <%= form.text_field :title, id: :article_title, class: 'siimple-input' %>
  </div>

  <div class="siimple-form-field">
    <%= form.label :body, class: 'siimple-label' %>
    <%= form.text_area :body, id: :article_body, class: 'editable siimple-textarea siimple-textarea--fluid' %>
  </div>

  <div class="siimple-form-field">
    <%= form.submit class: 'siimple-btn siimple-btn--blue' %>
  </div>
<% end %>

articles/new.html.erb

app/views/articles/new.html.erb

<h1 class="siimple-h2">New Article</h1>

<%= render 'form', article: @article %>

<%= link_to 'Back', articles_path, class: 'siimple-link' %>

articles/edit.html.erb

app/views/articles/edit.html.erb

<h1 class="siimple-h2">Editing Article</h1>

<%= render 'form', article: @article %>

<%= link_to 'Show', @article, class: 'siimple-link' %> |
<%= link_to 'Back', articles_path, class: 'siimple-link' %>

7. carrierwaveを使って画像を保存出来るようにする

以上までで、投稿が出来るようになったのですが、文中に挿入した画像を保存出来るようにします。

Gemfile

Gemfileを開き、以下を追加

gem 'carrierwave'

追加したら、bundle install

uploaderを生成

rails g uploader Image

画像保存用のリソースの作成

rails g scaffold Image file:string

imageモデルの修正

生成されたapp/models/image.rbを以下のように修正

class Image < ApplicationRecord
  mount_uploader :file, ImageUploader
end

routes.rbにpostメソッドを加える

config/routes.rbを開き、以下を加える

post 'images/upload', to: 'images#upload'

images_controller.rb

app/controllers/images_controller.rbを以下のように編集する。

scaffoldで生成したアクションは全て消してしまって大丈夫です。

class ImagesController < ApplicationController
  def upload
    files = params.require(:files)

    @image = Image.new
    @image.file = files[0]
    respond_to do |format|
      if @image.save
        format.html { redirect_to @image, notice: 'Image was successfully created.' }
        format.json do
          render json: {
            files:
              [
                {
                  url: @image.file.metadata['url']+"?id=#{@image.file.model.id}",
                  thumbnail_url: @image.file.metadata['url']+"?id=#{@image.file.model.id}",
                  size: 0,
                  delete_url: "/images/delete",
                  delete_type: "DELETE"
                }
              ]
          }
        end
      else
        format.html { render :new }
        format.json { render json: @image.errors, status: :unprocessable_entity }
      end
    end
  end
end

articles/_form.html.erbを修正する

app/views/articles/_form.html.erbの最終行に追加したscriptタグの中身を変更する。

<script>
  $(document).ready(function(){
    var editor = new MediumEditor('.editable', {
      // placeholder settings
      placeholder: {
        text: 'Type your story',
        hideOnClick: true
      }
    });

    $('.editable').mediumInsert({
      editor: editor,
      addons: {
        images: {
          fileUploadOptions: {
            url: '/images/upload',
            type: 'post',
            acceptFileTypes: /(.|\/)(gif|jpe?g|png)$/i
          }
        }
      }
    });
  });
</script>

8. 完成形のスクリーンショット

スクリーンショット 2018-05-05 23.50.53.png

スクリーンショット 2018-05-05 23.51.06.png

スクリーンショット 2018-05-06 0.24.40.png

スクリーンショット 2018-05-06 0.25.40.png

上記で一通り使えるまでには出来たかと思います。
そのうち、画像のストレージをCloudinaryに置き換えて画像を保存する方法について書こうと思います。

何かあればコメントください。

12
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7